简体   繁体   English

如何使用自定义日志记录处理程序将记录器重定向到 wxPython textCtrl?

[英]How can I redirect the logger to a wxPython textCtrl using a custom logging handler?

I'm using a module in my python app that writes a lot a of messages using the logging module.我在我的 python 应用程序中使用了一个模块,该模块使用日志记录模块编写了很多消息。 Initially I was using this in a console application and it was pretty easy to get the logging output to display on the console using a console handler.最初我在控制台应用程序中使用它,使用控制台处理程序很容易将日志输出显示在控制台上。 Now I've developed a GUI version of my app using wxPython and I'd like to display all the logging output to a custom control — a multi-line textCtrl.现在我已经使用 wxPython 开发了我的应用程序的 GUI 版本,并且我想将所有日志记录输出显示到一个自定义控件 - 一个多行 textCtrl。 Is there a way i could create a custom logging handler so i can redirect all the logging output there and display the logging messages wherever/however I want — in this case, a wxPython app.有没有办法我可以创建一个自定义的日志记录处理程序,这样我就可以在那里重定向所有的日志输出,并在我想要的任何地方/任何地方显示日志消息——在这种情况下,是一个 wxPython 应用程序。

Create Handler创建处理程序

import wx
import wx.lib.newevent

import logging

# create event type
wxLogEvent, EVT_WX_LOG_EVENT = wx.lib.newevent.NewEvent()


class wxLogHandler(logging.Handler):
    """
    A handler class which sends log strings to a wx object
    """
    def __init__(self, wxDest=None):
        """
        Initialize the handler
        @param wxDest: the destination object to post the event to 
        @type wxDest: wx.Window
        """
        logging.Handler.__init__(self)
        self.wxDest = wxDest
        self.level = logging.DEBUG

    def flush(self):
        """
        does nothing for this handler
        """


    def emit(self, record):
        """
        Emit a record.

        """
        try:
            msg = self.format(record)
            evt = wxLogEvent(message=msg,levelname=record.levelname)            
            wx.PostEvent(self.wxDest,evt)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

Then in your control然后在你的控制

self.Bind(EVT_WX_LOG_EVENT, self.onLogEvent)

def onLogEvent(self,event):
    '''
    Add event.message to text window
    '''
    msg = event.message.strip("\r")+"\n"
    self.logwindow.AppendText(msg) # or whatevery
    event.Skip()

Here's a simple working example:这是一个简单的工作示例:

import logging
import random
import sys
import wx

logger = logging.getLogger(__name__)

class WxTextCtrlHandler(logging.Handler):
    def __init__(self, ctrl):
        logging.Handler.__init__(self)
        self.ctrl = ctrl

    def emit(self, record):
        s = self.format(record) + '\n'
        wx.CallAfter(self.ctrl.WriteText, s)

LEVELS = [
    logging.DEBUG,
    logging.INFO,
    logging.WARNING,
    logging.ERROR,
    logging.CRITICAL
]

class Frame(wx.Frame):

    def __init__(self):
        TITLE = "wxPython Logging To A Control"
        wx.Frame.__init__(self, None, wx.ID_ANY, TITLE)

        panel = wx.Panel(self, wx.ID_ANY)
        log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100),
                          style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
        btn = wx.Button(panel, wx.ID_ANY, 'Log something!')
        self.Bind(wx.EVT_BUTTON, self.onButton, btn)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)
        handler = WxTextCtrlHandler(log)
        logger.addHandler(handler)
        FORMAT = "%(asctime)s %(levelname)s %(message)s"
        handler.setFormatter(logging.Formatter(FORMAT))
        logger.setLevel(logging.DEBUG)

    def onButton(self, event):
        logger.log(random.choice(LEVELS), "More? click again!")

if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = Frame().Show()
    app.MainLoop()

Screenshot:截屏:

运行脚本截图

Update: As iondiode points out, this simple script may have problems if there are multiple threads in your app, all logging via such a handler;更新:正如 iondiode 所指出的,如果您的应用程序中有多个线程,那么这个简单的脚本可能会出现问题,所有线程都通过这样的处理程序进行记录; ideally only a UI thread should update the UI.理想情况下,只有一个 UI 线程应该更新 UI。 You can use the suggested approach of logging the event by using a custom event, as per his answer.根据他的回答,您可以使用建议的方法通过使用自定义事件来记录事件。

You will need to create a custom logging.Handler and add it to your logging.Logger .您将需要创建一个自定义logging.Handler并将其添加到您的logging.Logger中。

From the documentation:从文档中:

Handler objects are responsible for dispatching the appropriate log messages (based on the log messages' severity) to the handler's specified destination. Handler程序对象负责将适当的日志消息(基于日志消息的严重性)分派到处理程序的指定目的地。 Logger objects can add zero or more handler objects to themselves with an addHandler() method. Logger 对象可以使用 addHandler() 方法向自己添加零个或多个处理程序对象。 As an example scenario, an application may want to send all log messages to a log file, all log messages of error or higher to stdout, and all messages of critical to an email address.作为示例场景,应用程序可能希望将所有日志消息发送到日志文件,将所有错误或更高级别的日志消息发送到标准输出,并将所有关键消息发送到电子邮件地址。 This scenario requires three individual handlers where each handler is responsible for sending messages of a specific severity to a specific location.此方案需要三个单独的处理程序,其中每个处理程序负责将特定严重性的消息发送到特定位置。

See http://docs.python.org/library/logging.html#handler-objects for the Handler API.有关Handler程序 API,请参阅http://docs.python.org/library/logging.html#handler-objects

In particular, it is the Handler.emit(record) method that you can implement to specify the destination of the output.特别是,您可以实现Handler.emit(record)方法来指定输出的目的地。 Presumably, you would implement this to call TextCtrl.AppendText .大概,你会实现这个来调用TextCtrl.AppendText

Answer回答

A general solution for custom log routing might look something like this:自定义日志路由的通用解决方案可能如下所示:

import logging

class CustomLogHandler(logging.Handler):
    def __init__(self, handler: "function"):
        logging.Handler.__init__(self)
        self.handler = handler
    def emit(self, record)
        record = self.format(record)
        self.handler(record)

Exmaple示例

This custom handler would take in a function that would be called with the log message every time a logger using this handler logs a message.此自定义处理程序将接收一个函数,每次使用此处理程序的记录器记录一条消息时,该函数将与日志消息一起调用。 This means you can do something like this这意味着你可以做这样的事情

import logging

def myLogHandler(self, logMSG):
    print(logMSG)

logObj = logging.getLogger(name) # Name of your logger here (or root)
myCustHandler = CustomLogHandler(myLogHandler)
logObj.addHandler(myCustHandler)

myLogHandler would be called every time you log a message with logObj .每次使用myLogHandler记录消息时都会调用logObj As I wrote it, this would just print the log message to the console.正如我写的那样,这只会将日志消息打印到控制台。

In that function, you could set up a way to route that log message to your GUI and get the desired output.在该函数中,您可以设置一种方法将该日志消息路由到您的 GUI 并获得所需的输出。

Why does this not answer the question directly about wxPython?为什么这不能直接回答关于 wxPython 的问题?

I wanted to provide a general solution that was framework-agnostic.我想提供一个与框架无关的通用解决方案。

The handler class I wrote could still be used if you switched from wxPython to PyQt or Tkinter or any other framework/library.如果您从 wxPython 切换到 PyQt 或 Tkinter 或任何其他框架/库,我编写的处理程序类仍然可以使用。 All that would have to change is the handler function.唯一需要改变的是处理函数。 That makes this code much more reusable.这使得这段代码更加可重用。

Other Relevant Topics其他相关主题

For anyone reading this solution: If you are not sure why this code is more reusable research some topics like Dependency Injection and SOLID Design Principles if you are unfamiliar with them.对于阅读此解决方案的任何人:如果您不确定为什么此代码更可重用,请研究一些主题,例如依赖注入SOLID 设计原则,如果您不熟悉它们。 After reading a bit about those, the motivation for this particular design might become clearer.在阅读了一些关于这些的内容之后,这种特殊设计的动机可能会变得更加清晰。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM