介绍
这是对wxPython所提供的日志类的一个总体介绍。这里的“日志记录”一词具有广泛的含义,涵盖了程序的所有输出内容,而不仅仅是那些非交互式的消息。wxPython中包含的日志记录功能提供了基础的wx.Log类,该类定义了日志目标的标准接口,同时还提供了该类的几种标准实现方式,以及一系列可与它们配合使用的函数。
首先,使用这些日志类并不需要了解wx.Log类的具体细节。为此,你只需了解wx.LogDebug、wx.LogError、wx.LogMessage以及类似的函数即可。它们所有的语法都与Python的日志记录模块相同。
以下是所有这些函数:
- wx.LogFatalError 与wx.LogError类似,但它还会使用退出码3(通过调用标准函数abort() 来终止程序。与所有其他日志记录函数不同的是,此函数不能被日志目标覆盖。
- wx.LogError 是用于记录错误消息的函数,也就是说,用于记录那些必须展示给用户的消息。默认的处理方式是弹出一个消息框来告知用户相关情况。
- wx.LogWarning 用于记录警告信息。这些警告信息通常也会展示给用户,但不会中断程序的运行。
- wx.LogMessage 用于记录所有常规的信息性消息。默认情况下,这些消息也会显示在一个消息框中(不过这是可以更改的,详见下文)。
- wx.LogVerbose 用于记录详细输出。通常情况下,这种输出会被抑制,但如果用户希望了解有关程序运行进度的更多详细信息,也可以激活它(该函数还有另一个可能会让人混淆的名称,即wx.LogInfo)。
- wx.LogStatus 用于记录状态消息。如果活动的或指定的(作为第一个参数传入)wx.Frame有状态栏,这些消息将会显示在其状态栏中。
- wx.LogSysError 主要由wxPython自身使用,但在系统调用(API函数)失败后记录错误时可能也会派上用场。它会记录指定的消息文本,以及最后一个系统错误代码(根据平台不同,可能是errno或者Windows系统的GetLastError() 的返回值)和相应的错误消息。该函数的第二种形式会将错误代码显式地作为第一个参数传入。
- wx.LogDebug 是用于调试输出的合适函数。它仅在调试模式下(当预处理器符号__WXDEBUG__被定义时)才会执行相应操作,而在发布模式下(其他情况)则不会执行任何操作。请注意,在Windows系统下,你必须要么在调试器下运行程序,要么使用诸如DebugView(www.microsoft.com/technet/sys…)之类的第三方程序,才能真正看到调试输出内容。
- wx.LogTrace 和wx.LogDebug一样,仅在调试版本的构建中才会执行某些操作。将它设计成一个独立于 wx.LogDebug的函数的原因在于,通常会存在大量的跟踪消息,所以将它们与其他可能会被这些跟踪消息淹没的调试消息区分开来是有意义的。此外,该函数的第二个版本将跟踪掩码作为第一个参数,这使得能够进一步限制生成的消息数量。
这些函数的用法应该相当简单明了。不过,可能有人会问为什么不使用其他的日志记录工具,比如Python的日志记录模块呢。简单来说,其他那些工具都是非常优秀的通用机制,但它们并非真正为wxPython量身定制,而wxPython的这些日志类却是。然而,每个项目都有不同的需求,所以我们鼓励你研究所有的选择,然后使用最适合你的方案。
使用wxPython日志函数的一些优点如下:
- 可移植性
- 灵活性:wx.Log函数的输出可以根据其重要性被重定向或者完全抑制,这用传统方法要么无法做到,要么很难实现。例如,可以只记录错误消息,或者只记录错误消息和警告消息,而过滤掉所有的信息性消息。
- 完整性:通常,当某些操作失败时,应该向用户显示错误消息。我们来看一个相当简单但常见的文件错误情况:假设你正在将数据文件写入磁盘,但磁盘空间不足。实际的错误可能是在wxPython代码内部检测到的,所以调用函数并不真正清楚失败的确切原因,它只知道数据文件无法写入磁盘。然而,由于wxPython 在这种情况下使用了wx.LogError函数,确切的错误代码(以及相应的错误消息)将与关于数据文件写入错误的“高级”信息一起呈现给用户。
日志消息选择
默认情况下,大多数日志消息都是启用状态。具体来说,这意味着由wxPython代码自身记录的错误(例如,当它无法执行某些操作时)将被处理并显示给用户。要完全禁用日志记录,你可以使用wx.Log.EnableLogging方法,或者更常见的做法是使用wx.LogNull类,该类会临时禁用日志记录,并在其被销毁时将日志记录恢复到原始设置。
为了将日志记录限制为仅记录重要消息,你可以使用wx.Log.SetLogLevel方法,并传入例如wx.LOG_Warning值——这将完全禁用所有严重程度低于警告级别的日志消息,这样一来,wx.LogMessage输出的内容就不会再显示给用户了。
此外,对于不同的日志组件,可以分别设置日志级别。在说明这样做有何益处之前,让我们先解释一下什么是日志组件:它们不过是用于标识生成消息的组件或模块的任意字符串。从某种意义上说,它们是具有层级结构的,比如“foo/bar/baz”组件可被视为“foo”组件的子组件。并且所有组件都是未命名的根组件的子组件。
默认情况下,由wxPython记录的所有消息都源自“wx”组件或其某个子组件,比如“wx/net/ftp”,而由你自己的代码记录的消息会被分配一个空的日志组件。要改变这种情况,你需要将wx.LOG_COMPONENT定义为一个能唯一标识每个组件的字符串。例如,你可以默认给它赋值为“MyProgram”,然后在处理数据库的模块中把它重新定义为“MyProgram/DB”,在管理事务的部分将其定义为“MyProgram/DB/Trans”。然后你可以按以下方式使用wx.Log.SetComponentLevel:
# 禁用所有数据库错误消息,反正大家都知道数据库永远不会出问题
wx.Log.SetComponentLevel("MyProgram/DB", wx.LOG_FatalError)
# 但要启用事务的跟踪功能,因为不知为何我们所做的更改有时无法提交
wx.Log.SetComponentLevel("MyProgram/DB/Trans", wx.LOG_Trace)
# 同时启用来自wxPython动态模块加载机制的跟踪消息
wx.Log.SetComponentLevel("wx/base/module", wx.LOG_Trace)
请注意,为事务代码显式设置的日志级别会覆盖其父组件的日志级别,但默认情况下,所有其他数据库代码子组件会继承该设置,因此根本不会生成任何日志消息。
日志目标
在列举了通常用于记录消息的所有函数,以及说明了为什么要使用这些函数之后,我们现在来描述一下这一切是如何运作的。
wxPython中有日志目标的概念:它只是一个从wx.Log派生的类。因此,它实现了基类的虚函数,当记录消息时会调用这些虚函数。在任何时刻,只有一个日志目标是活动的,这个目标就是LogXXX系列函数所使用的目标。日志对象(即从wx.Log派生的类的对象)的常规用法是通过调用SetActiveTarget() 将其设置为活动目标,并且在后续所有对LogXXX系列函数的调用中,它都会被自动使用。
要创建一个新的日志目标类,你只需让它从wx.Log派生,并在其中重写wx.Log.DoLogRecord、wx.Log.DoLogTextAtLevel和wx.Log.DoLogText这几个函数中的一个或多个。第一个函数是最灵活的,它允许你更改消息的格式、动态过滤消息并将其重定向等等 —— 除了由wx.LogFatalError生成的消息之外,所有日志消息都会经过这个函数处理。如果你只是想将日志消息重定向到其他地方,而不改变它们的格式,那么应该重写wx.Log.DoLogTextAtLevel函数。最后,如果你只想重定向日志消息,并且目标位置不依赖于消息的日志级别,那么重写wx.Log.DoLogText函数就足够了。
有一些从wx.Log派生的预定义类,这些类对于了解如何创建一个新的日志目标类可能会有所帮助,当然,它们也可以不加任何修改地直接使用。这些类包括:
- wx.LogStderr:这个类将消息记录到C语言的标准错误流(stderr)中。
- wx.LogGui:这是wxPython应用程序的标准日志目标(如果不做任何设置,默认情况下会使用它),并且针对给定平台,它能对所有类型的消息进行最为合理的处理。
- wx.LogWindow:这个日志目标提供了一个“日志控制台”,它会收集应用程序生成的所有消息,并且还会将这些消息传递给之前的活动日志目标。日志窗口框架有一个菜单,用户可以通过它来清除日志、完全关闭日志窗口或者将所有消息保存到文件中。
- wx.LogBuffer:这个目标会将所有已记录的消息收集到一个内部缓冲区中,以便之后能一次性将这些消息展示给用户。
- wx.LogNull:最后这个日志类非常特殊:它什么都不做。可以实例化这个类的对象来(临时)抑制LogXXX系列函数的输出。
日志目标也可以组合使用:例如,你可能希望将消息重定向到其他地方(比如,重定向到一个日志文件),但同时也能像平常一样处理这些消息。为此,可以使用wx.LogChain、wx.LogInterposer和wx.LogInterposerTemp类。
多线程应用程序中的日志记录
从wxPython 2.9.1版本开始,可以在任何线程中安全地调用日志记录函数。从主线程之外的其他线程记录的消息会被缓冲起来,直到在主线程中调用wx.Log.Flush方法(这通常发生在空闲时间,也就是处理完所有待处理事件之后),届时这些消息才会真正输出。请注意,默认的GUI日志记录器本来就只在刷新时才输出消息,所以默认情况下,来自其他线程的消息显示时间与平常大致相同。不过,如果你定义了自定义的日志目标,消息可能会乱序输出,例如,来自主线程且时间戳较晚的消息可能会出现在来自其他线程且时间戳较早的消息之前。不过,wx.Log能保证每个线程记录的消息会按照记录的顺序显示。
另外要注意,wx.Log.EnableLogging方法以及使用该方法的wx.LogNull类仅会影响当前线程,也就是说,在调用EnableLogging(False) 之后,其他线程仍可能会生成日志消息。
自定义日志
要彻底改变日志记录行为,你可以定义一个自定义的日志目标。例如,你可以定义一个从wx.Log派生的类,该类会在主应用程序窗口中专门预留用于消息输出的某个区域显示所有日志消息,而不会通过模态消息框来打断用户的工作流程。
要使用你自定义的日志目标,你既可以使用你自定义的日志对象调用wx.Log.SetActiveTarget方法,也可以创建一个派生自wx.AppTraits的类,并在其中重写wx.AppTraits.CreateLogTarget虚方法,同时重写wx.App.CreateTraits方法,以返回你自定义的特性对象的实例。请注意,在后一种情况下,你应该做好在程序启动早期以及程序关闭期间处理日志消息的准备,例如,你不应依赖主应用程序窗口的存在。不过,当你的日志目标被使用时,你可以放心地假定图形用户界面(已经/仍然)可用,因为如果不可用,wxPython会自动切换为使用wx.LogStderr。
在派生类中可以重写几种方法来定制日志消息的处理,它们是:wx.Log.DoLogRecord、wx.Log.DoLogTextAtLevel以及wx.Log.DoLogText。
最后一个方法是最简单的:如果你只是想将日志输出重定向到其他地方,而不考虑消息的级别,那么你应该重写这个方法。如果你确实想以不同的方式处理不同级别的消息,那么你应该重写wx.Log.DoLogTextAtLevel方法。
此外,你可以自定义从各个组件(如时间戳、源文件信息、日志记录线程ID等等)构建完整日志消息的方式。这项任务由wx.LogFormatter类来执行,所以你需要从它派生一个自定义类,并重写其Format() 方法,以便以所需的方式构建日志消息。请注意,如果你只是需要修改(或隐藏)时间戳的显示,重写FormatTime() 方法就足够了。
最后,如果还需要对输出格式进行更多的控制,那么可以重写LogRecord() 方法,因为该方法允许根据日志级别来构建自定义消息,甚至可以根据消息的严重程度来执行完全不同的操作(例如,丢弃除警告和错误之外的所有消息,在屏幕上显示警告信息,并将错误消息转发给用户(或程序员)的手机 —— 这或许可以根据时间戳判断当前时区是白天还是黑夜来决定)。
使用跟踪掩码
请注意,在当前的wxPython版本中,使用日志跟踪掩码几乎不再有必要,因为通过为任何级别的不同日志语句使用不同的日志组件,就可以达到相同的效果。有关日志组件的更多信息,请参阅“日志消息选择”。
以下这些函数可以在不编写新的日志目标类的情况下(编写新类除了要花上几分钟时间外,确实能让你实现任何你想要的功能),对wx.Log的行为进行一些有限的定制。详细消息即跟踪消息,这些消息在发布模式下不会被禁用,而是由wx.LogVerbose生成。通常情况下,它们不会展示给用户,因为它们不太会引起用户的兴趣,但也可以被激活,比如,用来帮助用户查找程序的某些问题。
至于(真正的)跟踪消息,它们的处理方式取决于当前已启用的跟踪掩码:如果针对给定消息的掩码调用了wx.Log.AddTraceMask函数,那么该消息将会被记录下来,否则不会有任何操作。
例如:
wx.LogTrace(wx.TRACE_OleCalls, "Foo.Bar() called")
如果在其之前有以下操作,将会记录该消息:
wx.Log.AddTraceMask(wx.TRACE_OleCalls)
标准的跟踪掩码在wx.LogTrace的文档中有说明。