System.out.println 提供了输出信息的一个基本功能,但println往往不是输出信息的最佳选择。要使用 println,必须创建所有要记录的信息,包括时间戳、文件名和你希望所有消息都提供的其他信息;而且也没有办法轻松地做到想要消息的时候就启用,不想要消息的时候就禁用。一般来说,代码投入生产环境时,你可能不希望其中还留有println语句。出于这些原因,println语句非常适用于临时性的实验,在这种环境下可以很快有所收获;但是不能把它作为一种用于长期观察的手段,要想长期地观察代码中发生了什么,就要另做选择。日志系统(logging system)可以解决这些问题。
日志(log)就是规范地记录下来的信息。数百年来,船长都会对船上发生的事件记录航海日志,包括船的位置、温度、天气以及特殊的事件。对于计算机来说,日志也有类似的功用,用于记录程序的状态,以及程序执行时出现的事件。日志记录(logging)是指创建日志和写入日志的过程。
System.out.println只提供了非常原始的日志记录功能,servlet容器还需要提供标准的日志服务以便记录消息。只需对servlet调用log() 方法,就可以得到这个日志服务。
实验:调用 log方法
JSP要编译为一个servlet,所以可以向容器提供的日志服务发送消息,如以下JSP所示:
<%@page pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>JSP Page</title></head>
<body>
<c:forEach var="loopCount" begin="1" end="10" step="1" >
<c:set var="myCount" value="${loopCount-5}" />
<c:out value="${myCount}"/></br>
<% String message = “loopCount="
+ pageContext.findAttribute("loopCount")
+ " myCount="
+ pageContext.findAttribute("myCount");
this.log( message );
%>
</c:forEach>
</body>
</html>
录入以上代码,并命名为Example5.jsp。这与前面使用System.out.println的代码基本上是一样的,不过,这里不是把消息发送给标准输出,而是用下面的语句交给了容器日志文件。
this.log(message);
Tomcat 5 日志文件的输出如下:
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=1 myCount=-4
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=2 myCount=-3
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=3 myCount=-2
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=4 myCount=-1
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=5 myCount=0
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=6 myCount=1
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=7 myCount=2
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=8 myCount=3
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=9 myCount=4
2004-07-12 21:51:17 StandardContext[/BegJsp]jsp: loopCount=10 myCount=5
可以在Tomcat 安装目录的logs目录下找到这个日志文件,文件名通常是localhost _log.<<date_string>>.txt。在其中可以得到日期和时间戳信息,还有Tomcat日志系统为每个日志项预先规定的上下文信息。
实验解析
之所以能在JSP中调用log方法,是因为基于Web的JSP会编译为一个servlet,这个servlet实现(或扩展)了HttpServlet的合约。HttpServlet 又扩展了GenericServlet,通过GenericServlet类可以访问容器提供的一组日志函数。另外,还可以使用ServletContext访问日志函数。
可以使用容器提供的两个log方法:
q void ServletContext.log( String msg )。
q void ServletContext log( String msg, Throwable throwable )。
前面已经介绍了第一个log方法的使用。第二个方法可以用来记录出现的异常。一般来说,只有当异常得到了处理而且不会再次抛出时才应该记入日志。
相对于使用println语句,使用容器提供的日志系统有很多改进。不过,基于容器的日志系统也存在局限性。例如,我们无法控制把消息记入日志的时间。要想有所控制,就需要日志框架。
J2SE 1.4 中引入了日志框架作为Java语言的一个标准部分。日志框架为JVM中运行的所有类提供日志服务。因此,servlet和JSP都可以使用这些日志服务。
实验:使用JDK日志记录器
先来看一个使用日志框架的代码示例:
<%@page pageEncoding="UTF-8"%>
<%@page import="java.util.logging.Logger" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>JSP Page</title></head>
<body>
<% Logger logger = Logger.getLogger(this.getClass().getName()); %>
<c:forEach var="loopCount" begin="1" end="10" step="1" >
<c:set var="myCount" value="${loopCount-5}" />
<c:out value="${myCount}"/></br>
<% String message = "loopCount="
+ pageContext.findAttribute("loopCount")
+ " myCount="
+ pageContext.findAttribute("myCount");
logger.info( message );
%>
</c:forEach>
</body>
</html>
这个示例JSP命名为Example6.jsp。
实验解析
这个例子与前面的例子很类似,只是突出显示的代码行有所区别。import 语句导入了使用JDK日志系统所需的类:
<%@page import="java.util.logging.Logger" %>
接下来的一行得到Logger类的一个实例,以便后面使用:
<% Logger logger = Logger.getLogger(this.getClass().getName()); %>
要得到日志记录器,还要调用this.getClass().getname()传入一个类名(由这个类生成日志消息),这个类名将作为日志记录器的名字。以这个类名创建了日志记录器之后,如果以后基于同一个类名调用getLogger() ,会返回同一个日志记录器实例。
1. 使用LogManager类
不一定非得用类名来命名日志记录器,不过这通常是一个很好的想法。日志记录器由一个名为LogManager的类管理,这个类会控制日志记录器组成的一个层次结构。以上代码中没有显示出LogManager类,不过它会在后台工作。LogManager 利用点分隔的记录器名来控制和建立层次结构。换句话说,这个层次结构建立在点分隔名的基础上。日志记录器会从层次结构中其父记录器继承属性,如图3-3所示。名为examples.webapps.HelloJsp的日志记录器会先从exam- ples.webapps继承属性,然后从examples继承,最后从根日志记录器继承,如图中加粗的路径所示。

图3-3 日志记录器层次结构和继承的例子
2. 日志记录器和日志级别
每个日志记录器都有一个相关的日志级别用于控制了日志记录器要记录哪些消息。日志记录器只会记录不低于相关日志级别的日志消息。如果一个日志记录器没有设置日志级别,就会在日志记录器层次结构中从其父记录器继承日志级别。日志级别通过调用logger.setLevel()方法来设置。
JDK日志记录器支持的日志级别如表3-1所示。
表3-1 JDK日志记录器支持的日志级别
|
日 志 级 别 |
说 明 |
|
SEVERE |
最高级别,用于记录最重要的消息,如致命的程序错误 |
|
WARNING |
用于发送警告消息 |
|
INFO |
默认级别,除非级别属性有修改。这个级别用于记录信息性消息 |
|
CONFIG |
在设置新的配置或配置设置有变时报告配置设置 |
|
FINE |
用于提供更详细的信息。通常在诊断或调试问题时使用 |
|
FINER |
比FINE提供的信息更详细 |
|
FINEST |
最低级别,用于记录最详细的消息 |
还有另外两个级别:ALL和OFF,ALL启用所有日志级别,OFF禁用所有日志级别。这两个级别用作为工具来控制日志输出,而不能用于发送消息。
如果发送的消息级别低于日志记录器所设置的日志级别,这个消息就不会发送给处理程序(稍后将谈到),也不会写到控制台、文件或其他任何输出设备。
可以使用一些便利函数在任何级别发送消息,如severe()、warning()、info()、config()、fine()、finer()和finest(),或者使用方法log(Level, message)来达到目的。此外还有一些函数可以记录异常和使用资源包。
下面是示例中发送日志消息的那行代码:
logger.info( message );
这行代码在INFO 级记录一个消息。如果使用以下代码:
logger.fine( message );
就会在FINE 级记录消息。
在这个示例JSP中,消息在INFO级发送。因为在JVM作为SDK的一部分安装时,根日志记录器配置的默认日志级别是INFO。这里之所以在INFO 级发送消息,目的是让例子更简单,不用修改日志记录器的属性,也不用改变任何配置就能运行。如果没有设置任何属性,日志记录器会继承其父日志记录器的属性。如果父日志记录器也没有设置属性,则会进一步从层次结构中的上一层继承,直到继承根日志记录器的属性值。
如前所述,日志记录器配置为会接受不低于某个级别的消息。默认地,日志记录器会配置为接受INFO或以上级别的消息。如果没有修改这个默认配置却使用logger.fine() 来记录消息,所记录的消息就不会出现在日志中。因此,可以使用这个特性来控制日志系统的输出级别,而无需修改代码。
一般地,调试消息会在FINE、FINER或FINEST 级别输出。在测试和调试期间执行代码时,日志记录器要配置为接收在FINE或更高级别上记录的消息。等代码投入生产环境时,再把日志记录器重新配置为忽略INFO级以下的消息。FINE、FINER和FINEST 提供了3种不同的详细程度用来完成调试。通常,我们只需要其中的一种级别就行了。
3. 日志处理器
要掌握JDK日志系统是如何工作的,需要了解3个主要的类,前面我们已经介绍了其中的两个。首先回顾一下这3个类:
q java.util.logging.Logger。
q java.util.logging.LogManager。
q java.util.logging.Handler。
日志记录器提供了按不同详细程度记录消息的方法。日志管理器基于记录器名的层次结构来管理日志记录器。创建日志记录器时会给定一个名字,这样才能放在LogManager管理的层次结构中。由于组成了一个层次结构,日志记录器就能从其父记录器继承属性,还可以从层次结构中其他的祖先记录器继承属性。
第3个类前面提到过,不过还需要更详细地讨论。日志处理器(handler)用于确定日志消息如何发送到输出设备。日志记录器得到一个消息时,它会调用一个或多个日志处理器。每个日志处理器分别向一个输出设备发送消息。输出设备可以是控制台、文件,甚至可以是数据库,不过输出设备要由日志处理器确定。每个日志记录器可能有多个日志处理器,这样只需对日志记录器做一次调用,就能把每个消息记录到多个位置上。日志记录器可以从其父记录器继承处理器,就像由父记录器继承日志级别设置一样。类似于日志记录器,每个日志处理器可以配置为接受或忽略某个指定级别或更低级别上记录的消息。日志处理器的设置与日志记录器的设置是独立的。
由于有这些特点,因此如果把日志记录器的级别设置为FINE,而不是INFO,然后使用logger.fine()来记录消息,消息仍然不会记录到输出设备上,除非把日志处理器也配置为能接受不低于FINE级别的消息。要记录一个消息,日志记录器以及这个日志记录器所用的处理器都必须适当地设置级别,它们的级别都要等于或低于所记录消息的级别。
日志记录器和日志处理器的日志级别必须设置为等于或低于所需消息的级别,只有这样这些消息才会出现在日志输出中。
这说明,可以有一个日志处理器将所有不低于WARNING 级别的消息发送到文件,同时这个日志记录器还连接有另一个日志处理器将FINE级别或更高级别上记录的所有消息发送到控制台。
常用的日志处理器类有3个:
q ConsoleHandler: 将输出发送到 System.err。
q FileHandler: 将输出发送到文件。
q SocketHandler: 将输出发送到网络流连接。
安装SDK时,ConsoleHandler会配置为默认日志处理器。
4. 配置SDK日志框架
默认地,SDK日志框架读取一个属性文件来完成配置。默认文件位于<JAVA_HOME> /jre/lib 目录,文件名是logging.properties。修改这个文件会影响到使用该JVM运行的所有程序;因此,建议在修改这个文件之前先做一个备份。修改配置日志记录器的属性文件只需在启动JVM时增加一个命令行参数,如下:
java -Djava.util.logging.config.file=mylog.properties MyClass
下面是一个简单的示例配置:
############################################################
# Global properties
############################################################
handlers= java.util.logging.ConsoleHandler
.level= INFO
# Sends all log messages to the console
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Logger Specific properties
############################################################
org.apache.jsp.level = FINE
这正是常规Java属性文件的格式。以#打头的代码行是注释。
handlers= java.util.logging.ConsoleHandler
以上代码指定了使用的一组日志处理器类,如果有多个处理器类,则用逗号分隔。在这里,只使用了一个日志处理器类,即Consolehandler 。
.level= INFO 将日志记录器(日志管理器LogManager所管理的层次结构中的根记录器)的默认日志级别设置为INFO。不要忘了前面的点号。
下面这一行将ConsoleHandler 配置为接受所有日志级别(ALL)的消息:
java.util.logging.ConsoleHandler.level = ALL
这里没有讨论格式器(Formatter),这不是本章要讨论的内容。默认地,ConsoleHandler会关联SimpleFormatter 。下面这行代码显式地把格式器设置为SimpleFormatter(没有这一行也完全可以,因为它是默认的格式器,这里主要是为了强调这一点):
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
还有其他一些格式器,如XMLFormater ,它采用XML格式来格式化日志消息。
紧接着是最后一行,这是一个特殊化设置,它将示例JSP配置为在FINE级别记录日志,而且使用org.apache.jsp层次结构中日志记录器的所有其他JSP也都在FINE级别记录日志(org.apache.jsp是Tomcat中JSP文件的默认层次结构):
org.apache.jsp.level = FINE
这个设置会覆盖根日志记录器的默认设置(默认设置为INFO 日志级别)。
在下一个实验中,需要对现在的文件做一些修改。
实验1:利用 SDK日志框架记录日志
首先,建立logging.properties 文件的一个副本,再将原文件替换为前面所列的属性文件。必须先把Tomcat停下来,再重启,这样才会读新的logging.properties 文件。接下来,对前面使用的example6.jsp文件做一点修改,将
logger.info( message );
改为
logger.fine( message );
再保存这个JSP文件。
这一次运行这个JSP文件时,会看到控制台输出或Tomcat stderr.log 文件中有以下记录:
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=1 myCount=-4
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=2 myCount=-3
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=3 myCount=-2
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=4 myCount=-1
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=5 myCount=0
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=6 myCount=1
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=7 myCount=2
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=8 myCount=3
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=9 myCount=4
Jul 16, 2004 12:33:26 AM org.apache.jsp.Sample5_jsp _jspService
FINE: loopCount=10 myCount=5
实验解析
替换后的logging.properties 文件指示日志消息要发送到控制台。Tomcat会在控制台显示这些消息,并将其记入日志文件。日志级别设置为FINE,所以JSP生成的所有消息都会记录下来。尝试之前,需要对现有的文件做一些修改。为了禁止输出,要把logging.properties 文件中的最后一行
org.apache.jsp.level = FINE
修改为
org.apache.jsp.level = INFO
然后再重启servlet容器。
每次想对日志配置做修改时都得停止再重启servlet容器,这很麻烦。在下面的实验中,我们将了解到如何修改日志设置而无需重启容器。
实验2:修改日志设置而无需重启容器
下面的脚本展示了如何强制重新读取配置文件,而不用重启JVM。请输入以下代码:
<%@page pageEncoding="UTF-8"%>
<%@page import="java.util.logging.LogManager" %>
<%@page import="java.util.logging.Logger" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>Log Reset</title></head>
<body>
<%
LogManager lm = LogManager.getLogManager();
lm.readConfiguration();
Logger logger = Logger.getLogger("org.apache.jsp");
out.println("Logger("+logger.getName()+") is now set to "+logger.getLevel());
%>
</body>
</html>
把这个文件保存为ResetLogger.jsp。如果想重新允许输出,把logging.properties 中的最后一行:
org.apache.jsp.level = INFO
改为:
org.apache.jsp.level = FINE
下面,不用重启容器,只需执行ResetLogger.jsp 再次记录消息。
实验解析
下面这两行代码:
LogManager lm = LogManager.getLogManager();
lm.readConfiguration();
得到LogManager的实例,并让它重新读取配置文件。
Logger logger = Logger.getLogger("org.apache.jsp");
out.println("Logger("+logger.getName()+") is now set to "+logger.getLevel());
这两行得到org.apache.jsp 日志记录器实例,并报告日志级别的当前设置。如果日志级别为null(空),则从父日志记录器继承级别设置。可以试试看,把logging.properties 文件的最后一行设置为:
org.apache.jsp.level = INFO
并执行。此时控制台上不会出现日志消息。接下来,把logging.properties 文件中的这行设置改为:
org.apache.jsp.level = FINE
再执行ResetLogger.jsp。输出如下:
Logger(org.apache.jsp) is now set to FINE
再来运行原来的示例JSP,控制台上(或stderr.log中)就会出现日志记录器输出。
Log4j是一个开源日志框架,出现至今已经有很多年了。在SDK日志框架露面之前,许多人都认为Log4j就是Java应用的标准日志框架。大多数开发人员还是喜欢用Log4j,而不想用内置的SDK Logger系统,因为Log4j非常灵活,功能也很强大。Log4j可以部署并配置Log4j JAR文件,使它作为Web应用的一部分,而不必像SDK日志记录器那样全局配置,这个好处是内置的SDK日志系统所没有的。
Eample7.jsp将示例文件修改为使用Log4j:
<%@page pageEncoding="UTF-8"%>
<%@page import="org.apache.log4j.Logger" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>JSP Page</title></head>
<body>
<%
Logger logger = Logger.getLogger(this.getClass().getName());
%>
<c:forEach var="loopCount" begin="1" end="10" step="1" >
<c:set var="myCount" value="${loopCount-5}" />
<c:out value="${myCount}"/></br>
<% String message = "loopCount="
+ pageContext.findAttribute("loopCount")
+ " myCount="
+ pageContext.findAttribute("myCount");
logger.info( message );
%>
</c:forEach>
</body>
</html>
要把Log4j JAR文件复制到Web应用的WEB-INF/lib目录,并把log4j.prop-
erties文件复制到WEB-INF/classes目录,再来运行这个JSP。log4j.properties文件设置会在这一节后面再做介绍。
以上代码中只有一行加了阴影,这是对前一个例子唯一的修改。类似于上一节讨论的SDK Logger,Log4j维护了日志记录器的层次结构。这个层次结构表示为日志类别(category)名的列表,各类别名之间用点号分隔,日志记录器从树中的祖先记录器继承配置信息。获取日志记录器时,要使用类别名。在下面这行代码中,传递给getLogger方法的串就表示类别:
Logger logger = Logger.getLogger("org.apache.jsp.TryLog4j.jsp");
用同一个类别名来获取日志记录器,总会得到同一个日志记录器实例。这个例子中用于获取日志记录器的类别如下:
q org.apache.jsp。
q org.apache。
q org。
q root(没有名字)。
以上是按继承顺序来显示的。树根没有名字,不过可以通过调用Logger.getRootLog-
ger()获取。
每个日志记录器都有一个相关的日志级别。如果没有日志级别,日志记录器则从有日志级别的最近祖先继承。根日志记录器肯定有日志级别,层次结构中的所日志记录器都能继承这个日志级别。
利用日志级别可以避免记录某些消息。默认的日志级别如表3-2所示。
表3-2 Log4j的默认日志级别
|
日 志 级 别 |
说 明 |
|
FATAL |
最高级别,程序要中止时使用 |
|
ERROR |
严重错误,但是程序可能还可以继续运行 |
|
WARN |
用于发送警告消息 |
|
INFO |
用于发送信息性消息 |
|
DEBUG |
最低级别,用于调试 |
除此以外,还有两个用于配置日志记录器的级别:ALL和OFF。ALL 接受所有日志消息,OFF 禁止所有消息。在不低于当前消息相应级别上记录的消息会发送到附加器(下面将介绍)。如果记录消息的级别低于日志记录器的级别,这些消息就会丢掉。
1. 附加器和布局
Log4j框架有3个主要组件:日志记录器、附加器(Appender)和布局(Layout)。日志记录器前面已经讨论,除此以外,Log4j还定义了附加器和布局。附加器用于确定日志项的输出目标,与SDK Logger中的日志处理器很类似。每个日志记录器都有许多与之关联的附加器。日志记录器把消息发送给与该记录器相关的所有附加器,以及与其祖先记录器关联的所有附加器。与SDK日志系统的日志处理器不同,附加器没有级别设置。一旦将消息发送给附加器,附加器就会把消息发送到输出设备。
SDK没有提供太多的日志处理器,Log4j则提供了更多的预建附加器。除了文件、控制台和socket附加器,Log4j还提供了另外一些附加器,可以把日志记录到数据库、基于JMS的消息系统、基于GUI的控制台系统、NT事件日志以及UNIX系统日志等。还有很多不同类型的基于文件的日志记录器,可以处理文件回滚(rollover)和其他一些常见的日志文件问题。
每个附加器都有一个相关的布局。布局对象用于确定记入日志的消息记录的格式。它与SDK Logger中的格式器作用相同,不过,Log4j提供了更丰富的布局。SimpleLayout 类可以提供与SDK Logger中的SimpleFormatter相当的基本日志特性。更常用的是PatternLayout,它接受一个模式或模板并用来对日志消息进行格式化。模式利用一系列token建立,每个token前面有一个百分号(%)。许多token后面可以有一个可选的精度限定符(precision specifier)(放在一对大括号里)。表3-3列出了可用来创建模式的各种token:
表3-3 用于创建模式的token
|
Token |
作 用 |
|
%c{1} |
输出日志记录器的类别。精度限定符是一个常整数。如果给定了精度限定符,只会打印最右类别名。例如,对于类别 "a.b.c", %c{2}会打印"b.c" |
|
%C{1} |
输出发送日志请求的调用者的完全限定类名。如果给定了精度限定符,只会打印类名的最右部分。例如,对于类名"com.wrox.jsp.MyServlet",%C{1}会打印"MyServlet" |
|
%d{ISO8601} |
用于输出日期。日期转换限定符后面可以有一个用大括号括起的日期格式限定符。例如,%d{HH:mm:ss,SSS} 或 %d{dd MMM yyyy HH:mm:ss,SSS}。如果没有给定日期格式限定符,则使用ISO8601格式 |
|
%f |
输出发出日志请求的文件名 |
|
%l |
用于输出生成日志事件的调用者的位置信息。位置通常包括文件名、方法和行号。这个操作可能相当慢 |
|
%L |
输出发出日志请求的相应行号。这个操作可能相当慢 |
|
%m |
输出发送给日志记录器的消息 |
|
%M |
输出要记录消息的方法名。这可能是一个比较慢的操作 |
|
%n |
输出独立于平台的行分隔符(换行或行结束符) |
|
%p |
输出日志事件的优先级,即记录事件的相应级别:FATAL、ERROR、WARN等等 |
|
%r |
输出自应用开始到日志事件发生为止已经过去了多长时间,以毫秒为单位 |
|
%t |
输出生成事件的线程的名字 |
|
%x |
输出与创建事件的线程相关的一个嵌套诊断上下文(nested diagnostic context,NDC) |
|
%X{mapkey} |
输出与创建事件的线程相关的一个映射诊断上下文(mapped diagnostic context,MDC)。映射的键必须放在token后面的大括号里 |
|
%% |
输出一个百分号 |
更多详细内容请见Javadoc中有关PatternLayout类的说明。嵌套诊断上下文和映射诊断上下文的概念会在本章后面讨论。百分号和token之间还可以有一些格式限定符(format specifier)来控制token输出的值的填充、截断和对齐方式。例如,减号表示左对齐。
2. 配置Log4j
Log4j会在其类路径中查找一个名为log4j.properties的文件以完成自动配置。对于一个Web应用,可以把log4j.properties 文件放在WEB-INF/classes目录中。下面是一个示例配置文件:
# Set root logger level to INFO and its only Appender to ConsoleOut.
log4j.rootLogger=INFO, ConsoleOut
# ConsoleOut is set to be a ConsoleAppender.
log4j.appender.ConsoleOut=org.apache.log4j.ConsoleAppender
# ConsoleOut is set to use PatternLayout.
log4j.appender.ConsoleOut.layout=org.apache.log4j.PatternLayout
log4j.appender.ConsoleOut.layout.ConversionPattern=%-5p: %d [%t] %c{1} - %m%n
这里的注释解释了配置文件所做的大部分工作。首先,为rootLogger 给定一个默认设置,日志级别为INFO ,而且只有一个相关的附加器,名为ConsoleOut。设置日志记录器级别后可以有一组附加器,各附加器之间用逗号分隔。如果需要额外的附加器,可以增加到ConsoleOut附加器后面(本例中只有一个附加器)。
用于实现ConsoleOut 附加器的类是ConsoleAppender。另外指定PatternLayout作为ConsoleOut附加器的布局类。然后为PatternLayout 实例提供了一个模式:
log4j.appender.ConsoleOut.layout.ConversionPattern=%-5p: [%d] %c{1} - %m%n
这个模式会打印日志消息的优先级,字段宽为5个字符,而且左对齐;然后打印一个冒号,再在方括号中打印ISO8601格式的日期和时间。然后打印日志类别的最后一个元素。如果是用类名来建立日志类别,最后的元素就是类名。再后面是一个连字符,然后打印日志消息,最后是行结束符。
实验:用Log4j记录日志
假设已创建了Example7.jsp 文件,并提供了前面给出的Log4j配置文件。运行这个示例JSP,观察日志结果,会得到以下日志项:
INFO : [2004-07-16 23:00:35,828] TryLog4j_jsp - loopCount=1 myCount=-4
INFO : [2004-07-16 23:00:35,828] TryLog4j_jsp - loopCount=2 myCount=-3
INFO : [2004-07-16 23:00:35,828] TryLog4j_jsp - loopCount=3 myCount=-2
INFO : [2004-07-16 23:00:35,828] TryLog4j_jsp - loopCount=4 myCount=-1
INFO : [2004-07-16 23:00:35,828] TryLog4j_jsp - loopCount=5 myCount=0
INFO : [2004-07-16 23:00:35,828] TryLog4j_jsp - loopCount=6 myCount=1
INFO : [2004-07-16 23:00:35,828] TryLog4j_jsp - loopCount=7 myCount=2
INFO : [2004-07-16 23:00:35,828] TryLog4j_jsp - loopCount=8 myCount=3
INFO : [2004-07-16 23:00:35,921] TryLog4j_jsp - loopCount=9 myCount=4
INFO : [2004-07-16 23:00:35,921] TryLog4j_jsp - loopCount=10 myCount=5
要修改所测试JSP的日志级别,只需在配置文件中增加一行。因为原来在INFO 级别记录消息,所以还应当把示例JSP修改为在DEBUG 级别记录日志。
将原JSP中的
logger.info( message );
修改为:
logger.debug( message );
做此修改后,会在DEBUG 级别发送消息。接下来在log4j.properties 配置文件的最后增加下面这行代码,并重启容器:
log4j.logger.org.apache.jsp=DEBUG
现在重新执行示例JSP,就会在DEBUG 级别记录JSP生成的日志项。不过,对于不是由JSP编译器创建的类,相应的日志项仍然在INFO 级别记录。
实验解析
日志记录方法info()和debug() 会在相应的优先级别向日志记录器发送消息。JSP中的Logger.getLogger()保证消息会通过org.apache.jsp之下的某个类别发送。对配置文件的最后一个修改

