Python logging to Tkinter Text Widget

¿Alguien por ahí tiene un ejemplo de cómo configurar el registro en Python a un widget de texto Tkinter? He visto esto usado en varias aplicaciones, pero no puedo entender cómo dirigir el registro a otra cosa que no sea un archivo de registro.

Además de las respuestas anteriores: aunque hay muchas soluciones propuestas para esto (aquí y también en este otro hilo ), luché bastante para hacer que esto funcionara. Finalmente, me encontré con esta clase de controlador de texto de Moshe Kaplan , que utiliza un widget ScrolledText (que probablemente sea más fácil que el método ScrollBar).

Me tomó algo de tiempo descubrir cómo usar realmente la clase de Moshe en una aplicación con hilos. Al final, creé una secuencia de comandos de demostración mínima que muestra cómo hacer que todo funcione. Como podría ser útil para otros, lo comparto a continuación. En mi caso particular, quería iniciar sesión tanto en la GUI como en un archivo de texto; si no necesita, simplemente elimine el atributo de nombre de archivo en logging.basicConfig .

import time import threading import logging try: import tkinter as tk # Python 3.x import tkinter.scrolledtext as ScrolledText except ImportError: import Tkinter as tk # Python 2.x import ScrolledText class TextHandler(logging.Handler): # This class allows you to log to a Tkinter Text or ScrolledText widget # Adapted from Moshe Kaplan: https://gist.github.com/moshekaplan/c425f861de7bbf28ef06 def __init__(self, text): # run the regular Handler __init__ logging.Handler.__init__(self) # Store a reference to the Text it will log to self.text = text def emit(self, record): msg = self.format(record) def append(): self.text.configure(state='normal') self.text.insert(tk.END, msg + '\n') self.text.configure(state='disabled') # Autoscroll to the bottom self.text.yview(tk.END) # This is necessary because we can't modify the Text from other threads self.text.after(0, append) class myGUI(tk.Frame): # This class defines the graphical user interface def __init__(self, parent, *args, **kwargs): tk.Frame.__init__(self, parent, *args, **kwargs) self.root = parent self.build_gui() def build_gui(self): # Build GUI self.root.title('TEST') self.root.option_add('*tearOff', 'FALSE') self.grid(column=0, row=0, sticky='ew') self.grid_columnconfigure(0, weight=1, uniform='a') self.grid_columnconfigure(1, weight=1, uniform='a') self.grid_columnconfigure(2, weight=1, uniform='a') self.grid_columnconfigure(3, weight=1, uniform='a') # Add text widget to display logging info st = ScrolledText.ScrolledText(self, state='disabled') st.configure(font='TkFixedFont') st.grid(column=0, row=1, sticky='w', columnspan=4) # Create textLogger text_handler = TextHandler(st) # Logging configuration logging.basicConfig(filename='test.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # Add the handler to logger logger = logging.getLogger() logger.addHandler(text_handler) def worker(): # Skeleton worker function, runs in separate thread (see below) while True: # Report time / date at 2-second intervals time.sleep(2) timeStr = time.asctime() msg = 'Current time: ' + timeStr logging.info(msg) def main(): root = tk.Tk() myGUI(root) t1 = threading.Thread(target=worker, args=[]) t1.start() root.mainloop() t1.join() main() 

Github Gist enlace al código anterior aquí:

https://gist.github.com/bitsgalore/901d0abe4b874b483df3ddc4168754aa

Debe subclase logging.Handler , por ejemplo:

 import logging from Tkinter import INSERT class WidgetLogger(logging.Handler): def __init__(self, widget): logging.Handler.__init__(self) self.widget = widget def emit(self, record): # Append message (record) to the widget self.widget.insert(INSERT, record + '\n') 

Aproveché la idea de Yuri, pero necesitaba hacer algunos cambios para que las cosas funcionaran:

 import logging import Tkinter as tk class WidgetLogger(logging.Handler): def __init__(self, widget): logging.Handler.__init__(self) self.setLevel(logging.INFO) self.widget = widget self.widget.config(state='disabled') def emit(self, record): self.widget.config(state='normal') # Append message (record) to the widget self.widget.insert(tk.END, self.format(record) + '\n') self.widget.see(tk.END) # Scroll to the bottom self.widget.config(state='disabled') 

Tenga en cuenta que es necesario alternar el estado de “normal” a “deshabilitado” para que el widget de Text de solo lectura.

¡Construyendo sobre el vado también pero agregando texto coloreado!

 class WidgetLogger(logging.Handler): def __init__(self, widget): logging.Handler.__init__(self) self.setLevel(logging.DEBUG) self.widget = widget self.widget.config(state='disabled') self.widget.tag_config("INFO", foreground="black") self.widget.tag_config("DEBUG", foreground="grey") self.widget.tag_config("WARNING", foreground="orange") self.widget.tag_config("ERROR", foreground="red") self.widget.tag_config("CRITICAL", foreground="red", underline=1) self.red = self.widget.tag_configure("red", foreground="red") def emit(self, record): self.widget.config(state='normal') # Append message (record) to the widget self.widget.insert(tk.END, self.format(record) + '\n', record.levelname) self.widget.see(tk.END) # Scroll to the bottom self.widget.config(state='disabled') self.widget.update() # Refresh the widget 

Sobre la base de la respuesta de Ford, aquí hay un widget de texto con desplazamiento que sigue el registro. El miembro logging_handler es lo que agrega a su registrador.

 import logging from Tkinter import END, N, S, E, W, Scrollbar, Text import ttk class LoggingHandlerFrame(ttk.Frame): class Handler(logging.Handler): def __init__(self, widget): logging.Handler.__init__(self) self.setFormatter(logging.Formatter("%(asctime)s: %(message)s")) self.widget = widget self.widget.config(state='disabled') def emit(self, record): self.widget.config(state='normal') self.widget.insert(END, self.format(record) + "\n") self.widget.see(END) self.widget.config(state='disabled') def __init__(self, *args, **kwargs): ttk.Frame.__init__(self, *args, **kwargs) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=0) self.rowconfigure(0, weight=1) self.scrollbar = Scrollbar(self) self.scrollbar.grid(row=0, column=1, sticky=(N,S,E)) self.text = Text(self, yscrollcommand=self.scrollbar.set) self.text.grid(row=0, column=0, sticky=(N,S,E,W)) self.scrollbar.config(command=self.text.yview) self.logging_handler = LoggingHandlerFrame.Handler(self.text) 

Me he topado con el mismo problema. La solución común encontrada fue como se mencionó en este widget de traer hilos del módulo GUI al Logging.Handler.
Fue criticado por no ser de seguridad, ya que un widget pasa a otro hilo (registro).
La mejor solución encontrada fue usar la cola, hecha por Benjamin Bertrand: https://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget/ .
Lo mejoré un poco: la cola se debe crear en el controlador de registro y pasar a un widget GUI.

 importar registrador
 cola de importación


 clase QueueHandler (logging.Handler):
     def __init __ (self):
         super () .__ init __ ()
         self.log_queue = queue.Queue ()

     def emitir (auto, grabar):
         # poner un mensaje formateado en cola
         self.log_queue.put (self.format (registro))


 queue_handler = QueueHandler ()
 logger.addHandler (queue_handler)

Por lo tanto, generalmente tenemos el registrador disponible, y cada mensaje de registro de cualquier módulo se pasará a un queue_handler junto con otros controladores, a un archivo, etc. Ahora podemos importar queue_handler a un widget.
La mayor parte del código se toma del enlace anterior. Acabo de cambiar la ubicación de la cola de registro original.

 de tkinter.scrolledtext import ScrolledText
 de some_logging_module import queue_handler

 clase ConsoleUi:
     "" "Sondee los mensajes de una cola de registro y los muestre en un widget de texto desplazado" ""

     def __init __ (self, frame, queue):
         self.frame = frame
         # Crear un wdiget ScrolledText
         self.console = ScrolledText (frame)
         self.console.configure (font = 'TkFixedFont')
         self.console.pack (padx = 10, pady = 10, fill = BOTH, expand = True)
         self.log_queue = cola
         # Comenzar a sondear mensajes de la cola
         self.frame.after (100, self.poll_log_queue)

     def display (self, msg):
         self.console.configure (estado = 'normal')
         self.console.insert (END, msg + '\ n')
         self.console.configure (state = 'disabled')
         # Desplazamiento automático hacia abajo
         self.console.yview (END)

     def poll_log_queue (self):
         # Compruebe cada 100 ms si hay un mensaje nuevo en la cola para mostrar
         Si bien no es self.log_queue.empty ():
             msg = self.log_queue.get (bloque = False)
             auto.display (msg)
         self.frame.after (100, self.poll_log_queue)

Como resultado, un widget muestra todos los datos de registro de una aplicación.