Reputation: 51
Having spent some time on redirecting stdout and logging output to a tkinter text widget, I've decided I need some help. My code is as follows:
#!/usr/bin/env python
from Tkinter import *
import logging
from threading import Thread
class IODirector(object):
def __init__(self,text_area):
self.text_area = text_area
class StdoutDirector(IODirector):
def write(self,str):
self.text_area.insert(END,str)
def flush(self):
pass
class App(Frame):
def __init__(self, master):
self.master = master
Frame.__init__(self,master,relief=SUNKEN,bd=2)
self.start()
def start(self):
self.master.title("Test")
self.submit = Button(self.master, text='Run', command=self.do_run, fg="red")
self.submit.grid(row=1, column=2)
self.text_area = Text(self.master,height=2.5,width=30,bg='light cyan')
self.text_area.grid(row=1,column=1)
def do_run(self):
t = Thread(target=print_stuff)
sys.stdout = StdoutDirector(self.text_area)
t.start()
def print_stuff():
logger = logging.getLogger('print_stuff')
logger.info('This will not show')
print 'This will show'
print_some_other_stuff()
def print_some_other_stuff():
logger = logging.getLogger('print_some_other_stuff')
logger.info('This will also not show')
print 'This will also show'
def main():
logger = logging.getLogger('main')
root = Tk()
app = App(root)
root.mainloop()
if __name__=='__main__':
main()
I know that one can define a new logging handler based on a text widget but I can't get it working. The function "print_stuff" is really just a wrapper around many different functions all having their own logger set up. I need help with defining a new logging handler that is "global" so that it can be instantiated from each of the functions having their own logger. Any help is much appreciated.
Upvotes: 5
Views: 8964
Reputation: 123481
Here's a completely revised answer that does what you want. I've tried to indicate which lines of the code in your question were change and where new line were added.
By default the built-inlogger.StreamHandler
handler class outputs messages to sys.stderr
, so to have them also redirectedsys.stdout
the text widget requires constructing a new logger with a custom console handler set up to do that. Since you want this to apply to all loggers in the module, this setting needs to be applied to the nameless "root" logger that all other named loggers will inherit their setting from.
from Tkinter import *
import logging
from threading import Thread
class IODirector(object):
def __init__(self, text_area):
self.text_area = text_area
class StdoutDirector(IODirector):
def write(self, msg):
self.text_area.insert(END, msg)
def flush(self):
pass
class App(Frame):
def __init__(self, master):
self.master = master
Frame.__init__(self, master, relief=SUNKEN, bd=2)
self.start()
def start(self):
self.master.title("Test")
self.submit = Button(self.master, text='Run', command=self.do_run, fg="red")
self.submit.grid(row=1, column=2)
self.text_area = Text(self.master, height=2.5, width=30, bg='light cyan')
self.text_area.grid(row=1, column=1)
def do_run(self):
t = Thread(target=print_stuff)
sys.stdout = StdoutDirector(self.text_area)
# configure the nameless "root" logger to also write # added
# to the redirected sys.stdout # added
logger = logging.getLogger() # added
console = logging.StreamHandler(stream=sys.stdout) # added
logger.addHandler(console) # added
t.start()
def print_stuff():
logger = logging.getLogger('print_stuff') # will inherit "root" logger settings
logger.info('This will now show') # changed
print 'This will show'
print_some_other_stuff()
def print_some_other_stuff():
logger = logging.getLogger('print_some_other_stuff') # will inherit "root" logger settings
logger.info('This will also now show') # changed
print 'This will also show'
def main():
logging.basicConfig(level=logging.INFO) # enable logging # added
root = Tk()
app = App(root)
root.mainloop()
if __name__=='__main__':
main()
Upvotes: 3
Reputation: 868
Just to make sure I understand correctly:
You want to print the logging messages both to your STDout and Tkinter text widget but the logging won't print in the standard console.
If it is indeed your problem here's how to do it.
First let's make a very simple console in Tkinter, it could be any text widget really but I'm including it for completeness:
class LogDisplay(tk.LabelFrame):
"""A simple 'console' to place at the bottom of a Tkinter window """
def __init__(self, root, **options):
tk.LabelFrame.__init__(self, root, **options);
"Console Text space"
self.console = tk.Text(self, height=10)
self.console.pack(fill=tk.BOTH)
Now let's override the logging Handlers to redirect to a console in parameter and still automatically print to STDout:
class LoggingToGUI(logging.Handler):
""" Used to redirect logging output to the widget passed in parameters """
def __init__(self, console):
logging.Handler.__init__(self)
self.console = console #Any text widget, you can use the class above or not
def emit(self, message): # Overwrites the default handler's emit method
formattedMessage = self.format(message) #You can change the format here
# Disabling states so no user can write in it
self.console.configure(state=tk.NORMAL)
self.console.insert(tk.END, formattedMessage) #Inserting the logger message in the widget
self.console.configure(state=tk.DISABLED)
self.console.see(tk.END)
print(message) #You can just print to STDout in your overriden emit no need for black magic
Hope it helps.
Upvotes: 5