Catch stderr in non-console applications

Bryan Olson fakeaddress at nowhere.org
Fri Nov 4 14:08:36 EST 2005


New and improved!

Love Python's stack-tracing error messages, but hate the way
GUI applications throw the messages away and crash silently?

Here's a module to show Python error messages that would
otherwise be lost in console-less programs.  Graphical
applications should not require console windows, but they do
need a workable destination for sys.stderr.

To try it out, you can use something like:

     import errorwindow
     x = undefined_variable_name


I've tried this version on Microsoft Windows 2000 and XP, and
on a couple versions of Linux. I'd be happy to hear how it does
on other systems.

Thanks to George (g... at ll.mit.edu) for testing a previous version.
Thanks to Robert Kern for pointing me to a bug solution.


--Bryan

---------------- cut -------------------
#!/usr/bin/env python

#   Python module "errorwindow.py", by Bryan Olson, 2005.
#   This module is free software and may be used, distributed,
#   and modified under the same terms as Python itself.

"""

     Importing this module redirects sys.stderr so that error
     output, if any, will appear in a new window.

     Notes on the module:

         It is particularly handy for graphical applications that
         do not otherwise have a stderr stream. It mitigates most
         silent failures.

         It uses only this file plus facilities in the Python
         standard distribution.

         There are no functions to call; just import it.

         Import it early; it cannot catch prior errors.

         It can catch syntax errors in modules imported after
         it, but not in '__main__'.

         Importing it more than once is harmless.

         When there is no error output, it is highly efficient,
         because it does nothing.

         Upon output to sys.stderr, it runs a new process and
         pipes the error output.

         It does not import any graphical library into your
         program. The new process handles that.


"""

import sys
import os
import thread


if __name__ == '__main__':

     from threading import Thread
     from Tkinter import *
     import Queue
     queue = Queue.Queue(99)
     def read_stdin(app):
         while 1:
             data = os.read(sys.stdin.fileno(), 2048)
             queue.put(data)
             if not data:
                 break
     class Application(Frame):
         def __init__(self, master=None):
             Frame.__init__(self, master)
             self.master.title("Error stream from %s" % sys.argv[-1])
             self.pack(fill=BOTH, expand=YES)
             self.grid_rowconfigure(0, weight=1)
             self.grid_columnconfigure(0, weight=1)
             xscrollbar = Scrollbar(self, orient=HORIZONTAL)
             xscrollbar.grid(row=1, column=0, sticky=E+W)
             yscrollbar = Scrollbar(self)
             yscrollbar.grid(row=0, column=1, sticky=N+S)
             self.logwidget = Text(self, wrap=NONE,
                     xscrollcommand=xscrollbar.set,
                     yscrollcommand=yscrollbar.set)
             self.logwidget.grid(row=0, column=0, sticky=N+S+E+W)
             xscrollbar.config(command=self.logwidget.xview)
             yscrollbar.config(command=self.logwidget.yview)
             # Disallow key entry, but allow copy with <Control-c>
             self.logwidget.bind('<Key>', lambda x: 'break')
             self.logwidget.bind('<Control-c>', lambda x: None)
             self.after(200, self.start_thread, ())
         def start_thread(self, _):
             t = Thread(target=read_stdin, args=(self,))
             t.setDaemon(True)
             t.start()
             self.after(200, self.check_q, ())
         def check_q(self, _):
             go = True
             while go:
                 try:
                     data = queue.get_nowait()
                     if not data:
                         Label(self, text="Stream closed.", relief=
                                 "sunken").grid(row=2, sticky=E+W)
                         go = False
                     self.logwidget.insert(END, data)
                     self.logwidget.see(END)
                 except Queue.Empty:
                     self.after(200, self.check_q, ())
                     go = False
     app = Application()
     app.mainloop()

else:

     class ErrorPipe(object):
         def __init__(self):
             self.lock = thread.allocate_lock()
             self.command = " ".join([sys.executable, __file__, 
sys.argv[0]])
         def write(self, data):
             self.lock.acquire()
             try:
                 if not hasattr(self, 'pipe'):
                     self.rawpipe = os.popen(self.command, 'w')
                     fd = os.dup(self.rawpipe.fileno())
                     self.pipe = os.fdopen(fd, 'w', 0)
                 self.pipe.write(data)
             finally:
                 self.lock.release()

     sys.stderr = ErrorPipe()



More information about the Python-list mailing list