views:

162

answers:

2

Here's the thing:

I'm trying to combine the logging module with wx.App()'s redirect feature. My intention is to log to a file AND to stderr. But I want stderr/stdout redirected to a separate frame as is the feature of wx.App.

My test code:

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

Expected behavior is:
- a file is created named test.log
- the file contains logging messages with level DEBUG and INFO/ERROR/WARNING/CRITICAL
- messages from type INFO and ERROR/WARNING/CRITICAL are either shown on the console or in a separate frame, depending on where they are created
- logger messages that are not inside MyApp or MyFrame are displayed at the console
- logger messages from inside MyApp or MyFrame are shown in a separate frame

Actual behavior is:
- The file is created and contains:

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.

- Console output is:

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

- No separate frame is opened, although the lines

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

shouldget displayed within a frame and not on the console.

It seems to me that wx.App can't redirect stderr to a frame as soon as a logger instance uses stderr as output. wxPythons Docs claim the wanted behavior though, see here.

Any ideas?

Uwe

A: 

When wx.App says it will redirect stdout/stderr to a popup window, what it means really is that it will redirect sys.stdout and sys.stderr, so if you directly write to sys.stdout or sys.stderr it will be redirected to a popup window e.g. try this

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

Problem here is that if wxApp is created after creating streamhandler, streamhandler is pointing to old(original) sys.stderr and sys.stdout not to the new ones which wxApp has set, so a simpler solution is to create wx.App before creating streap handler e.g. in code move app = MyApp(redirect = True) before logging initialization code.

Alternatively create a custom logging handler and write data to sys.stdout and sys.stderr or better create you own window and add data there. e.g. try this

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)
Anurag Uniyal
>logging doesn't write to sys.stdout or sys.stderr This is not quite true (if the docs are right(http://docs.python.org/library/logging.html#module-logging.handlers)). As the docs state, sys.stderr is use if an instance of logging.handlers.StreamHandler is created without argument.
Uwe
@Uwe, i have updated answer, you need to move wx.App creation before Streamhandler creation
Anurag Uniyal
Unfortunately, this won't work either as all logger instances created within MyApp or MyFrame won't be connected to the actual logger "main". "main"'s stream is redirected to wx's redirection-frame but "main.MyApp" and "main.MyFrame" loggers are no children of "main" as "main" doesn't exist upon creation of "main.MyApp" and "main.MyFrame". And as they have not set a formatter and handler, the don't know where to put the debug messages from within MyApp and MyFrame.
Uwe
only option you have to write custom handlers
Anurag Uniyal
I decided to use Gabriel Genellina's LoggingWebMonitor (http://code.activestate.com/recipes/577025-loggingwebmonitor-a-central-logging-server-and-mon/) as solution.
Uwe
@Uwe, but what that has to do with wx redirect?
Anurag Uniyal
I wanted debug messages in a separate window. Now I get them in a browser, not in a wx frame. But as it seems impossible on the first look to redirect a loggers stream to wx this was the time saving way. I tried writing an own logging handler but failed. And this solution covers both log messages created within wx and outside.
Uwe
in that case, you could just open the log file in a good editor or `tail -f xxx.log` but anyway your question should have stated that, I have answered the question not the hidden objective you had
Anurag Uniyal
A: 

The way I do this, which I think is more elegant, is to create a custom logging Handler subclass that posts its messages to a specific logging frame.

This makes it easier to turn GUI logging on/off at runtime.

wbg