将Python日志记录器的输出重定向到tkinter部件

5

在将stdout重定向和记录输出到tkinter文本小部件上花费了一些时间后,我决定需要一些帮助。我的代码如下:

#!/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()

我知道可以基于文本小部件定义一个新的日志处理程序,但我无法使其正常工作。函数“print_stuff”实际上只是许多不同函数的包装器,每个函数都有自己的记录器设置。我需要帮助定义一个新的日志处理程序,它是“全局”的,以便可以从具有自己日志记录器的每个函数中实例化。非常感谢您的帮助。
2个回答

5

我来确认一下我的理解:

您想将日志信息同时输出到STDout和Tkinter文本小部件,但是日志不能在标准控制台中打印。

如果确实是这个问题,以下是解决方法:

首先,让我们在Tkinter中创建一个非常简单的控制台,它可以是任何文本小部件,但我包括它以便完整起见:

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)

现在让我们重写日志处理程序以将日志重定向到参数中的控制台,并仍然自动打印到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

希望这有帮助。

我建议在__init__函数中删除对pack的调用,否则您只能在使用pack的框架中使用此类。通常情况下,如果创建小部件的函数也负责其布局,那么您的代码将更易于使用,而不是让小部件自己进行布局。 - Bryan Oakley
1
当我在LabelFrame中使用多个小部件来创建'MegaWidget'时,我通常会在init中打包子小部件,这样用户只需将LabelFrame放置在所需位置,里面的所有内容都已经正确放置。但是在这个例子中,我删除了除文本小部件以外的所有内容,因此我可以看出它在这个例子中是无用的。 - Asics
哦,我的错!我没有在init中看到框架。是的,内部小部件应该在内部管理。对于造成的困惑,非常抱歉。 - Bryan Oakley

3
这里是一份完全修订过的答案,可以满足您的需求。我尽量指出了在您的问题中哪些代码行已被更改以及新行添加的位置。
默认情况下,内置的logger.StreamHandler处理程序类将消息输出到sys.stderr,因此为了将它们重定向到sys.stdout,需要构建一个带有自定义控制台处理程序的新记录器来实现。由于您希望这适用于模块中的所有记录器,因此需要将此设置应用于无名称的“root”记录器,所有其他命名记录器都将从中继承其设置。
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()

1
谢谢您的回复,但我已经有一种方法在我的代码中重定向stdout了。问题是Python日志记录器似乎不会将消息写入stdout,即使您将stdout明确添加为记录器的处理程序。至少当您重定向stdout时,来自记录器的消息不包括在内。 - user2073502

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接