日志记录
本文件面向对在 GWT 应用中记录客户端代码感兴趣的开发者。日志记录是记录应用中事件的过程,用于提供审计跟踪以了解应用如何执行以及诊断问题。日志记录使开发者和用户更容易排查遇到的问题。
以下部分将逐步介绍一个日志记录示例应用,并介绍日志记录框架的基本功能和配置选项。开发者应已熟悉 GWT 应用开发。
- 日志记录框架概述
- 添加 GWT 日志记录的超级简单方法
- 构建/运行日志记录示例
- 记录器、处理程序和根记录器
- 配置 GWT 日志记录
- 不同类型的处理程序
- 客户端与服务器端日志记录
- 远程日志记录
- 使所有日志记录代码编译时被移除
- 模拟和非模拟类
日志记录框架概述
日志记录框架模拟 java.util.logging,因此它使用相同的语法并具有与服务器端日志记录代码相同的行为。这使您能够在客户端代码和服务器端代码之间共享日志记录代码。有关 java 日志记录的详细概述,请参阅:http://download.oracle.com/javase/6/docs/technotes/guides/logging/overview.html;您应该熟悉 java.util.logging,以便更好地了解如何使用 GWT 日志记录。
与 java.util.logging 不同,GWT 日志记录是使用 .gwt.xml 文件配置的。您可以使用这些文件完全启用/禁用日志记录,启用/禁用特定处理程序,以及更改默认日志记录级别。当日志记录被禁用时,代码将被编译出来,因此您可以在开发/调试时使用日志记录,然后让您的生产版本编译时将其移除,以保持您的 JavaScript 大小较小。
添加 GWT 日志记录的超级简单方法
添加 GWT 日志记录非常简单,就像以下代码示例一样简单。但是——理解日志记录的工作原理以及如何正确配置它非常重要,因此请花时间阅读本文档的其余部分。
# In your .gwt.xml file
<inherits name="com.google.gwt.logging.Logging"/>
# In your .java file
Logger logger = Logger.getLogger("NameOfYourLogger");
logger.log(Level.SEVERE, "this message should get logged");
关于继承日志记录的警告:如果您没有继承 com.google.gwt.logging.Logging
,那么日志记录在技术上仍然可以正常工作,因为 Java 代码的模拟始终存在。但是,您将不会获得任何默认处理程序,也无法配置根记录器(如本文档所述)。某些库有时不继承 Logging,它们想要记录错误或信息,但不想控制客户应用如何显示这些信息(它们不想配置或打开日志记录,但如果库的用户打开了日志记录,它们确实想要使日志记录信息可用)。
构建/运行日志记录示例
您以与构建和运行其他 GWT 示例(如 Hello)相同的方式构建和运行 LogExample。svn 源代码中的 eclipse 目录包含一个 README.txt 文件以提供帮助。当您运行它时,将显示以下“日志记录示例”网页,其中包含弹出菜单和复选框,用于触发测试异常并查看它们的记录方式。
LogExample 是使用 LogExample.gwt.xml
配置的。应用的入口点是 LogExample.java
——它只是创建并向页面添加了各种演示模块。这些模块中的每一个都说明了不同的一组日志记录概念;本教程将逐步引导您完成这些概念。
记录器、处理程序和根记录器
记录器以树状结构组织,根记录器位于树的根部。父/子关系由记录器的名称确定,使用“.”来分隔名称的各个部分。因此,如果我们有两个记录器 Foo.Bar 和 Foo.Baz,那么它们是兄弟节点,它们的父节点是名为 Foo 的记录器。Foo 记录器(以及任何名称中不包含句点“.”的记录器)的父节点是根记录器。
当您向记录器记录消息时,如果消息的级别足够高,它会将消息传递给其父节点,父节点会将其传递给其父节点,以此类推,直到到达根记录器。在此过程中,任何给定的记录器(包括根记录器)也会将消息传递给它的任何处理程序,如果消息的级别足够高,这些处理程序将以某种方式输出消息(到弹出窗口、到 stderr 等)。有关此内容的更详细说明,请参阅 http://java.sun.com/j2se/1.4.2/docs/guide/util/logging/overview.html。
如果您打开 LogExample.java
,您可以看到我们创建了 3 个记录器
// <a href="https://gwt.googlesource.com/gwt/+/master/samples/logexample/src/com/google/gwt/sample/logexample/client/LogExample.java">LogExample.java</a>
private static Logger childLogger = Logger.getLogger("ParentLogger.Child");
private static Logger parentLogger = Logger.getLogger("ParentLogger");
private static Logger rootLogger = Logger.getLogger("");
我们将这 3 个记录器传递给 LoggerController
,后者依次为每个记录器创建一个 OneLoggerController
实例。在 OneLoggerController.java
中,您可以看到更改记录器级别、向记录器记录消息以及向记录器记录异常的示例代码。
// <a href="https://gwt.googlesource.com/gwt/+/master/samples/logexample/src/com/google/gwt/sample/logexample/client/OneLoggerController.java">OneLoggerController</a>
// Change the level of the logger
@UiHandler("levelTextBox")
void <b>handleLevelClick</b>(ChangeEvent e) {
Level level = Level.parse(levelTextBox.getItemText(
levelTextBox.getSelectedIndex()));
logger.log(Level.SEVERE,
"Setting level to: " + level.getName());
<b>logger.setLevel(level);</b>
}
// Log a message to the logger
@UiHandler("logButton")
void <b>handleLogClick</b>(ClickEvent e) {
Level level = Level.parse(logTextBox.getItemText(
logTextBox.getSelectedIndex()));
<b>logger.log(level, "This is a client log message");</b>
}
// Trigger an exception and log it to the logger
@UiHandler("exceptionButton")
void <b>handleExceptionClick</b>(ClickEvent e) {
try {
Level n = null;
n.getName();
} catch (NullPointerException ex) {
<b>logger.log(Level.SEVERE, "Null Exception Hit", ex);</b>
}
}
您可以使用这 3 个记录器进行尝试。目前,最容易查看日志消息的地方是 LogExample.java 在网页上创建的弹出窗口(不同处理程序将在下一节中讨论)。
配置 GWT 日志记录
在大多数简单的日志记录实现中,我们只需处理根记录器。所有消息都会向上传播到树的根记录器。根记录器附加的级别和处理程序决定哪些消息会被记录以及记录到哪里。这是 GWT 日志记录中默认处理程序配置背后的基本理念。
您可以配置的最简单的项目是根记录器的级别。您可以通过在 URL 中添加 logLevel
查询参数来实现。现在尝试一下,将“&logLevel=SEVERE”添加到示例 URL 中。请注意,所有记录器的默认级别现在已设置为 SEVERE 而不是 INFO。
配置 GWT 日志记录的另一种方法是通过 .gwt.xml 文件,如下所示
# LogExample.gwt.xml
<set-property name="gwt.logging.logLevel" value="SEVERE"/> # To change the default logLevel
<set-property name="gwt.logging.enabled" value="FALSE"/> # To disable logging
<set-property name="gwt.logging.consoleHandler" value="DISABLED"/> # To disable a default Handler
您可以尝试在提供的 LogExample.gwt.xml 文件中配置日志记录。
不同类型的处理程序
GWT 日志记录带有一组已定义并(默认情况下)附加到根记录器的处理程序。您可以像上面讨论的那样在 .gwt.xml 文件中禁用这些处理程序,扩展它们,将它们附加到其他记录器等等。您可以尝试使用复选框从根记录器中添加/删除各种处理程序——这背后的代码位于 HandlerController.java
中。请注意,如果您使用 .gwt.xml 文件禁用处理程序,那么它的任何实例都将被替换为 NullLogHandler(它什么都不做并被编译出来),因此无法使用复选框添加/删除处理程序。
以下是如何使用复选框添加或删除处理程序的示例
// <a href="https://gwt.googlesource.com/gwt/+/master/samples/logexample/src/com/google/gwt/sample/logexample/client/HandlerController.java">HandlerController.java</a>
public void onValueChange(ValueChangeEvent<Boolean> event) {
if (checkbox.getValue()) {
<b>logger.addHandler(handler);</b>
} else {
<b>logger.removeHandler(handler);</b>
}
}
大多数默认处理程序非常简单
SystemLogHandler
- 记录到标准输出。这些消息只能在开发模式下看到——在 DevMode 窗口中查找它们DevelopmentModeLogHandler
- 通过调用方法 GWT.log 进行记录。这些消息只能在开发模式下看到——在 DevMode 窗口中查找它们ConsoleLogHandler
- 记录到 javascript 控制台,后者由 Firebug Lite(用于 IE)、Safari 和 Chrome(?) 使用FirebugLogHandler
- 记录到 FirebugPopupLogHandler
- 记录到出现在左上角的弹出窗口SimpleRemoteLogHandler
- 在下面的远程日志记录部分中讨论
虽然 PopupLogHandler
易于使用,但它也有些侵入性。对于大多数应用来说,更好的解决方案是禁用 PopupLogHandler
,而是将日志消息发送到应用中的某个 Panel。GWT 日志记录的设置使这变得很容易,您可以在 CustomLogArea.java
中看到一个示例。在这种情况下,我们创建了一个 VerticalPanel
(虽然任何继承自 HasWidgets 并支持多个 add()
调用的 widget 都可以使用)。获得这些 widget 之一后,我们只需将其传递给 HasWidgetsLogHandler
的构造函数,并将该处理程序添加到记录器中。
// <a href="https://gwt.googlesource.com/gwt/+/master/user/src/com/google/gwt/user/client/ui/VerticalPanel.java">VerticalPanel.java</a>
VerticalPanel customLogArea;
// An example of adding our own custom logging area. Since VerticalPanel extends HasWidgets,
// and handles multiple calls to add(widget) gracefully we simply create a new HasWidgetsLogHandler
// with it, and add that handler to a logger. In this case, we add it to a particular logger in order
// to demonstrate how the logger hierarchy works, but adding it to the root logger would be fine.
logger.addHandler(new HasWidgetsLogHandler(customLogArea));
客户端与服务器端日志记录
虽然 GWT 模拟 java.util.logging,但了解服务器端日志记录和客户端日志记录之间的区别非常重要。客户端日志记录记录到客户端的处理程序,而服务器端日志记录记录到服务器端的处理程序。
为了说明清楚,客户端 GWT 代码有一个独立于服务器端代码的根记录器(和记录器层次结构);上面讨论的所有处理程序只适用于客户端代码。如果客户端和服务器共享的代码发出日志记录调用,那么它记录到哪个根记录器(和记录器层次结构)将取决于它是在客户端还是服务器端执行。您不应该在共享代码中添加或操作处理程序,因为这将无法按预期工作。
在 ServerLoggingArea.java
中,您可以试验这些概念。该部分中的按钮将触发服务器上的日志记录调用,以及来自客户端和服务器端的 SharedClass.java
中的日志记录调用。请注意客户端日志记录和服务器端日志记录之间格式的细微差异,以及每个日志记录到的不同处理程序(在本教程中,服务器端日志记录将简单地记录到 stderr,而客户端日志记录将记录到上面讨论的所有处理程序)。
远程日志记录
为了将客户端代码记录的事件存储在服务器端,您需要使用 RemoteLogHandler
。此处理程序将日志消息发送到服务器,在那里它们将使用服务器端日志记录机制进行记录。GWT 目前包含一个 SimpleRemoteLogHandler
,它将以最简单的方式(使用 GWT-RPC)执行此操作,并且没有智能批处理、失败情况下的指数级回退等等。此记录器默认情况下处于禁用状态,但您可以在 .gwt.xml 文件中启用它(有关配置默认处理程序的更多详细信息,请参阅上面的处理程序部分)。
# <a href="https://gwt.googlesource.com/gwt/+/master/samples/logexample/src/com/google/gwt/sample/logexample/LogExample.gwt.xml">LogExample.gwt.xml</a>
<set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />
您还需要提供 remoteLoggingServlet。
使所有日志记录代码编译出来
当日志记录被禁用时,编译器将使用延迟绑定将 Null 实现替换为 Logger 和 Level 类。由于这些实现只是返回 Null 并且不执行任何操作,因此它们通常会被 GWT 编译器(它在删除无用代码方面做得很好)修剪掉。但是,不能保证您编写的与日志记录相关的其他代码也会被编译出来。如果您想保证某些代码块在日志记录被禁用时被删除,您可以使用 LogConfiguration.loggingIsEnabled()
方法。
if (LogConfiguration.loggingIsEnabled()) {
String logMessage = doSomethingExpensiveThatDoesNotNormallyCompileOut();
logger.severe(logMessage);
}
通常编译出来的代码仍然存在于开发模式中。您可以使用与上面相同的条件来隐藏开发模式中的代码,如下所示。
// <a href="https://gwt.googlesource.com/gwt/+/master/samples/logexample/src/com/google/gwt/sample/logexample/client/CustomLogArea.java">VerticalPanel.java</a>
// Although this code will compile out without this check in web mode, the guard will ensure
// that the handler does not show up in development mode.
if (LogConfiguration.loggingIsEnabled()) {
logger.addHandler(new HasWidgetsLogHandler(customLogArea));
}
模拟和非模拟类
GWT 日志记录框架不会模拟 java.util.logging 的所有部分。有关模拟类和成员的列表,请参阅 JRE 模拟参考。
提供以下处理程序和格式化程序。
HTMLFormatter
TextFormatter
SystemLogHandler
ConsoleLogHandler
FirebugLogHandler
DevelopmentModeLogHandler
HasWidgetsLogHandler (and LoggingPopup to use with it)