用于指定 appender 将日志语句写入日志目的地所采用的格式。appender 可以用来格式化输出结果的各种布局包括:简单布局、模式布局和 HTML 布局。
[1]先看看Log4j的包目录结构:
org.apache.log4j:log4j主要的类、接口以及特殊的Appender类、Layout、Level和Logger
org.apache.log4j.spi:包含了针对SPI的一部分扩展【SPI——System Programming Interface】
org.apache.log4j.chainsaw:基于Java Swing的一个GUI日志查看器:
org.apache.log4j.config:用来设置或者获取某些组件的相关属性
org.apache.log4j.helpers:仅仅被log4j库内联使用的多个Class的集合,一般情况下不对外
org.apache.log4j.jdbc:一个Appender用来记录JDBC连接的相关事件
org.apache.log4j.jmx:在JMX开发的时候可以配置的基于JMX的日志记录,但是目前该包里面的类还不是特别稳定
org.apache.log4j.lf5:【目前很少用,我也不知道这部分用途,没用过】
org.apache.log4j.net:用来进行远程日志记录的Appender,主要用于JMS、SMTP以及基于Socket的日志记录,用于向一个log4j服务器发送日志进行远程的日志记录
org.apache.log4j.nt:Java本地接口的应用【JNI】,接口用Java,实现用C代码,用来记录Windows NT系统事件日志的Appender组件
org.apache.log4j.or:根据对象类型来对对象进行Render操作的帮助类
org.apache.log4j.performance:性能测试代码
org.apache.log4j.xml:包含了多个XML组件,使用DOM Tree的结构在Log4j环境里面用来记录XML格式的日志
org.apache.log4j.varia:包含了多个Appender以及Filters和其他相关组件
[2]Log4j的安装:
Log4j下载下来过后需要进行简单的安装,先保证原来的系统环境和Java环境是正常的:
- 下载log4j的jar包,上边已经提供了下载地址
- 将这些包解压到一个目录里面,【*:一般对于开发人员最好将jar的包分类放在不同的目录下边方便使用。】
- 将包下边的log4j-1.2.15.jar放入开发的CLASSPATH里面,关于Java里面CLASSPATH的一些问题留到开发用Java里面去讲解
- 还需要下载两个辅助解析XML的jar,下载地址http://archive.apache.org/dist/xml/xerces-j/,这里我下载的最新版本2.9.0,取出里面的xercesImpl.jar和xml-apis.jar两个jar文件放到CLASSPATH里面。【*:在IDE环境下有专程针对CLASSPATH的设置,这种情况防止jar冲突最好的方式就是保持CLASSPATH里面只有java标准的环境】
2)Log4j的核心概念:
[1]Log4j中的Logger:
Logger在Log4j中是一个很核心的概念,它是日志进程里面的核心组件,在Log4j里面,Logger总共分为六个日志记录级别,其定义位于org.apache.log4j.Level类里:
- TRACE:这种细粒度信息时间不仅仅针对调试,
- DEBUG:指出细粒度信息事件对调试应用程序是非常有帮助的,该级别的日志主要用于辅助开发人员调试;
- INFO:该级别表明消息上细粒度上突出强调了应用程序的运行过程
- WARN:表明有可能会出现潜在错误的情形
- ERROR:表明虽然程序发生了错误事件,但是仍然不影响系统的继续运行
- FATAL:该级别表明了每个严重的错误时间,将会导致应用程序的退出。
根据API里面的描述还存在两个很特殊的级别:
- ALL:这是最低级别,用于打开所有的日志记录
- OFF:这是最高级别,用于关闭所有的日志记录
一般情况下,推荐开发过程只使用ERROR、WARN、INFO、DEBUG四个级别,下图说明了这几个级别相互之间的关系:
从上图可以知道,只有当日志记录器Logger的日志级别高于或者等于输出消息的等级的时候,该日志消息才会被输出。默认情况下,Logger的级别是Debug,如果我们需要创建一个Logger,有以下几种方式:
创建根日志器:
Logger logger = Logger.getRootLogger();
创建一个新的日志器:
Logger logger = new Logger("MyLogger");
创建一个基于类的日志器:
Logger logger = new Logger(MyClass.class);
在Log4j里面设置一个日志的级别使用语句:
logger.setLevel(Level.DEBUG);
【*:在1.2版本之前,Log4j里面没有使用Logger类,主要是使用Category类,从版本1.2开始才使用Logger类,就上边的方式创建Logger】
这里先看一段代码,然后对Category进行一个详细说明,实际上从版本1.2开始Category和Logger是父子关系,Logger从Category类继承而来;在该版本之前我们一般使用Category来创建日志记录器,在前边的版本里面Category就等同于现在我们所见到的Logger。在版本1.2中,Category被标记为deprecated,而直接使用Logger替代它,一般情况下Java里面被标记为deprecated的方法和类都有替代方法以及类来进行对应功能的版本更新,我们一般在开发过程中尽量避免使用标记为deprecated的类或者方法。下边的代码是升级的改动:
// 被抛弃的代码形式:
Category cat = Category.getInstance("foo.bar");
// 目前使用的创建Logger的形式:
Logger logger = Logger.getInstance("foo.bar");
为了使得上边的图形里面的内容更加容易理解,这里提供另外一段代码:
package org.susan.java.logging;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
class Foo{}
public class Log4jLevelTester {
public static void main(String args[]){
Logger logger = Logger.getLogger(Foo.class);
logger.setLevel(Level.INFO);
// 该请求是被允许的,因为 WARN >= INFO
logger.warn("Low fuel level.");
// 该请求时背禁止的,因为 DEBUG < INFO
logger.debug("Starting search for nearest gas station.");
}
}
上边这段代码很好说明了logger在对待LEVEL的时候的用法,在setLevel的时候Logger设置了日志器本身的Level,然后在使用里面对应的warn、debug方法的时候会判断使用的方法和日志记录器设置的Level进行详细的比较,当满足上边图示的判断条件的时候才会输出日志信息。在这里还有一点需要注意的是方法warn和debug,这两个方法都不是Logger类所具有的方法,而是从它的父类继承过来的Category的方法,在使该方法的时候会针对我们最初设置的日志的Level进行一个比较和判断,最终决定是否要被记录下来。那么在环境配置好的情况下,我们执行该程序。
【异常】这里会遇到一个初学Log4j的常见问题,我们会发现控制台只有这句话输出:
log4j:WARN No appenders could be found for logger (org.susan.java.logging.Foo).
log4j:WARN Please initialize the log4j system properly.
这句话的意思是在系统环境里面没有找到我们所需要的配置文件,一般理解是缺乏log4j.properties属性文件,在此处,如果我们不使用配置文件的方式就将下边的代码修改成:
package org.susan.java.logging;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
class Foo{}
public class Log4jLevelTester {
public static void main(String args[]){
BasicConfigurator.configure();
Logger logger = Logger.getLogger(Foo.class);
logger.setLevel(Level.INFO);
// 该请求是被允许的,因为 WARN >= INFO
logger.warn("Low fuel level.");
// 该请求时背禁止的,因为 DEBUG < INFO
logger.debug("Starting search for nearest gas station.");
}
}
修改成上边这段代码过后,我们就可以直接在没有log4j.properties配置文件的情况下直接运行上边的程序,该代码将会有以下输出:
0 [main] WARN org.susan.java.logging.Foo - Low fuel level.
通过这里我们就可以知道Log4j里面的Level的排序为:DEBUG < INFO < WARN < ERROR < FATAL,等到后边再来分析上边的代码如果不使用BasicConfigurator.configure();而是使用我们开发常用的属性文件log4j.properties
进行日志记录的相关配置,关于配置文件的详细内容等介绍完Layout和Appender了过后再说明。
[2]Log4j中的Appender:
Log4j里面的Appender类似于JDK 1.4 Logging Framework里面的Handler组件,其主要目的是管理我们日志记录的结果,描述了这些日志怎样进行输出,下边列举了比较常用的一些Appender的快照:
- ConsoleAppender:这种Appender会管理我们的日志消息,将日志事件记录到控制台以System.out或者System.err作为输出机制,它默认的输出为System.out;
- RollingFileAppender:这种Appender从FileAppender继承过来,大部分属性和FileAppender是类似的,当日志记录的文件到达文件容量的最大值的时候,会自动创建一个新的日志文件
- FileAppender:将日志输出到普通文本文件【这里有点需要说明,好像从版本1.2开始该Appender将会标记为禁用,替代的方案为该类的类:WriterAppender和ConsoleAppender】
- DailyRollingFileAppender:该Appender从FileAppender继承而来,能够按照一定的频度滚动日志记录文件
- WriterAppender:根据用户的选择将日志的信息以数据流的方式发送到任何用户指定的地方
- SMTPAppender:在特殊的事件日志发生的时候,发送一封Email到指定的邮件地址,一般情况下是针对ERROR级别以及FATAL级别的错误进行这种Appender的配置
- SocketAppender:将发送一个LoggingEvent对象到一个远程的日志服务器,一般情况下是一个SocketNode对象
- SocketHubAppender:将发送一个LoggingEvent对象的集合到一个远程的日志服务器,一般情况下是一个SocketNodes
- SyslogAppender:将日志记录消息发送到配置好的一个syslog远程服务器上
- TelnetAppender:该Appender和SocketHubAppender类似,也是向服务器发送日志信息,但是不是一个SocketNode对象或者SocketNode对象列,一般发送的是Category【1.1版】输出的结果。
整个org.apache.log4j包结构里面,Appender的结构图如下:[I]标识接口,[A]标识抽象类,[C]标识具体类
接口:
Appender[I]
类层次结构:
AppenderSkeleton[A]
|—AsyncAppender[C]
|—org.apache.log4j.jdbc.JDBCAppender[C]
|—org.apache.log4j.net.JMSAppender[C]
|—org.apache.log4j.lf5.LF5Appender[C]
|—org.apache.log4j.nt.NTEventAppender[C]
|—org.apache.log4j.varia.NullAppender[C]
|—org.apache.log4j.net.SMTPAppender[C]
|—org.apache.log4j.net.SocketAppender[C]
|—org.apache.log4j.net.SocketHubAppender[C]
|—org.apache.log4j.net.SyslogAppender[C]
|—org.apache.log4j.net.TelnetAppender[C]
|—WriterAppender[C]
|—ConsoleAppender[C]
|—FileAppender[C]
|—RollingFileAppender[C]
|—org.apache.log4j.varia.ExternallyRolledFileAppender[C]
|—DailyRollingFileAppender[C]
AsyncAppender:该Appender将会使用异步的方式来进行日志记录,而且该Appender会将我们在日志记录中记录下来的相关信息,一旦发现有其他的Appender与之相关,就会将这些信息发送给相对应的Appender。这种Appender和其他Appender不一样的地方在于它在进行日志记录的过程中会开启一条新的线程(Thread)来完成日志记录。
【*:AsyncAppender是不能像其他日志记录器一样通过log4j.properties文件来配置,这个Appender只能使用DOMConfigurator通过编码的方式来进行配置】
这里提供一段使用AsyncAppender的代码:
package org.susan.java.logging;
import org.apache.log4j.AsyncAppender;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.TTCCLayout;
public class AsyncLogging {
private static Logger logger = Logger.getLogger("org.susan.java.logging.AsyncLogging");
private AsyncAppender asyncAppender = null;
private ConsoleAppender consoleAppender = null;
public AsyncLogging(){
try{
logger.setAdditivity(false);
asyncAppender = new AsyncAppender();
TTCCLayout layout = new TTCCLayout("yyyy-MM-dd");
consoleAppender = new ConsoleAppender(layout,"System.out");
asyncAppender.setName("Async");
asyncAppender.setBufferSize(5);
asyncAppender.setLocationInfo(true);
asyncAppender.setBlocking(false);
asyncAppender.activateOptions();
asyncAppender.addAppender(consoleAppender);
logger.addAppender(asyncAppender);
}catch(Exception ex){
ex.printStackTrace();
}
}
public void doLogging(){
logger.debug("Hello One");
logger.debug("Hello Two");
logger.debug("Hello Three");
}
public static void main(String args[]){
AsyncLogging demo = new AsyncLogging();
demo.doLogging();
}
}
上边这段代码,创建了一个AsyncAppender,并且将她与一个ConsoleAppender连接起来,实现日志记录的异步操作,运行上边这段代码将会得到以下结果:
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello One
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Two
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Three
需要注意的是下边的代码:
asyncAppender.addAppender(consoleAppender);
该代码段的就可以将我们所需要的Appender和AsyncAppender连接起来进行异步方式的日志记录。
这里遇到过一个问题,因为我查了很多资料没有找到相关的文档,就是这里AsyncAppender的异步方式的原理。
我个人觉得:AsyncAppender可以使得和它相关的所有连接的Appender进行关联过后异步操作,那么一旦在AsyncAppender里面添加了一个Appender过后,该日志记录会和AsyncAppender结合在一起实现异步日志记录,而且添加的每个Appender都是使用了和AsyncAppender相关的异步机制而使用新的线程去完整针对不同的输出源进行彼此不影响的并发操作,每个线程关联到不同的输出源,也就是说Appender作为一个异步日志记录的最初入口,使得在做日志记录里面的Appender可以实现异步机制,而Log4j里面的Appender内部原理本身应该就是异步的,但是内部的原理和外部相互之间Appender之间的结构没有直接的关联。按照这样的理解,每个AsyncAppender可以关联多个不同的AsyncAppender,于是我修改了上边代码段:
package org.susan.java.logging;
import org.apache.log4j.AsyncAppender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.TTCCLayout;
public class AsyncLogging {
private static Logger logger = Logger.getLogger("org.susan.java.logging.AsyncLogging");
private AsyncAppender asyncAppender = null;
private ConsoleAppender consoleAppender = null;
//这是新添加的一个Appender2作为asyncAppender去连接的一个新的ConsoleAppender
private ConsoleAppender consoleAppender2 = null;
public AsyncLogging(){
try{
logger.setAdditivity(false);
asyncAppender = new AsyncAppender();
TTCCLayout layout = new TTCCLayout("yyyy-MM-dd");
consoleAppender = new ConsoleAppender(layout,"System.out");
// 为了在Eclipse平台里面测试的时候可以更加明显知道谁在进行记录,所以第二个ConsoleAppender使用了System.err
TTCCLayout layout1 = new TTCCLayout("yyyy-MM-dd");
consoleAppender2 = new ConsoleAppender(layout1,"System.err");
asyncAppender.setName("Async");
asyncAppender.setBufferSize(5);
asyncAppender.setLocationInfo(true);
asyncAppender.setBlocking(false);
asyncAppender.activateOptions();
asyncAppender.addAppender(consoleAppender);
// 这里创建的异步Appender添加了第二个Appender与之关联起来
asyncAppender.addAppender(consoleAppender2);
logger.addAppender(asyncAppender);
}catch(Exception ex){
ex.printStackTrace();
}
}
public void doLogging(){
logger.debug("Hello One");
logger.debug("Hello Two");
logger.debug("Hello Three");
}
public static void main(String args[]){
AsyncLogging demo = new AsyncLogging();
demo.doLogging();
}
}
修改过后将会得到下边的输出:
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello One
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello One
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Two
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Three
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Two
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Three
这样我们推测的结果就一目了然了,上边红色部分是consoleAppender2输出的结果,灰色部分是consoleAppender输出的结果
JDBCAppender:1.2版本里面的JDBCAppender将来很有可能会被替换掉,不仅仅如此,它也不会用来记录相关异常。JDBCAppender主要提供了一个机制直接将日志写入到数据库,每一个Appender的调用都会添加到一个ArrayList里面作为缓冲,当这个缓冲里面填充了事件日志信息的时候,将会被JDBCAppender替代掉,然后根据相关配置生成对应的SQL脚本并且执行。在log4j.properties配置文件里面可以配置该Appender的BufferSize(缓冲区大小)、dbURL(数据库的URL地址)、User(数据库用户名)、Password(数据库密码)。在操作过程中,可以通过使用setSql方法来设置在日志记录中需要使用的SQL语句,这个SQL语句将会发送到PatternLayout对象【当然该对象是由用户定义的】,默认情况下所有的模式转换在PatternLayout格式化过程都是会出现在生成的SQL命令里面的。
有时候我们开发过程需要自己定义一个基于数据库的日志记录器,当JDBCAppender作为我们使用类的父类的时候,有几个操作是必须要注意的:
- 重写方法getConnection()来获取我们需要的连接,重写该方法主要是启用应用程序的连接池
- 重写closeConnection(Connectoin con)方法,如果我们重写了getConnection方法,那么应该重写closeConnection方法用来关闭我们所需要的连接,或者说将不使用的连接放入到我们需要的连接池里面
- 重写getLogStatement(LoggingEvent event)方法用来生成动态的语句,默认情况下我们直接使用SQL语句的可选值。
JMSAppender:在使用JMSAppender的过程中,事件消息是作为JMS消息定义的类型ObjectMessage在传输过程进行序列化和反序列化操作完成的,其使用模式为JMS Topic方式。
LF5Appender【不了解详情】:这种Appender将会把日志记录到基于日志控制台的Swing界面上,该Swing控制台支持各种类型、多种详细试图以及一个基于全文检索的日志搜索
NTEventLogAppender:这个Appender只能使用于Windows操作系统,而且在使用该Appender的时候,需要将Log4j解压过后里面的NTEventLogAppender.dll动态链接库配置到Windows系统的PATH环境变量里面,否则我们在开发过程将会收到JVM的错误信息:java.lang.UnsatisfiedLinkError
这里再提供一段将日志写入Windows事件查看器的代码:
package org.susan.java.logging;
import org.apache.log4j.Logger;
public class NTEventTester {
public static void main(String args[]){
Logger logger = Logger.getLogger("NTlog");
logger.info("This is the test info message");
// 只有这条记录会写入进去
logger.fatal("This is the test fatal message");
}
}
仅仅这段代码当然不够,看看这段代码的配置文件log4j.properties:
log4j.logger.NTlog = FATAL,stdout
log4j.appender.stdout=org.apache.log4j.nt.NTEventLogAppender
log4j.appender.stdout.Source=org.susan.java.logging.NTEventTester
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1};%L - %m%n
上边的配置后边会讲到,在配置章节讲log4j.properties的信息的时候,这里先不做说明,但是注意log4j.logger.NTlog里面的NTlog就是在代码里面通过Logger.getLogger("NTlog")里面的参数,而且边的等级指定了记录什么信息,运行这段代码可能控制台会有下边的输出:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no NTEventLogAppender in java.library.path
上边的错误就是上边讲到的没有配置NTEventLogAppender.dll的缘故,所以按照上边的配置方法需要将dll配置到PATH环境变量里面,这里再简单说明以下:
在32bit系统里面,可以直接把NTEventLogAppender.dll拷贝到C:/Windows/System32,这里C为安装Windows操作系统盘的盘符名称
在64bit系统里面的路径应该是:C:/Windows/SysWOW64,这一点不要搞错
配置好了过后控制台仍然没有相关输出,别着急,去Windows看看事件查看器就会出现下边的输出:
从截图可以看到,该事件的源为org.susan.java.logging.NTEventTester,也就是我们自己将这条记录写入到事件查看器里面去的。
NullAppender:上边已经说过了org.apache.log4j.varia里面包含了多个相关组件以及Filter组件,这个Appender可以将日志记录消息发送到任意设备
SMTPAppender:使用该Appender的时候,可以配置BufferSize参数来配置有多少个日志记录消息会通过邮件发出去。
这里再提供一份发送邮件的日志记录器的测试代码:
package org.susan.java.logging;
import org.apache.log4j.Logger;
public class EmailLogging {
public static void main(String args[]){
Logger logger = Logger.getLogger("Emaillog");
logger.info("This is the test info message");
logger.fatal("This is the test fatal message");
System.out.println("Success...");
}
}
这段代码本身和上边NT记录的本身没有太大的区别,而真正的区别在于下边的配置文件项:
log4j.logger.Emaillog = WARN,stdout
log4j.appender.stdout=org.apache.log4j.net.SMTPAppender
log4j.appender.stdout.SMTPHost=smtp.126.com
log4j.appender.stdout.Subject=ErrorLog
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1};%L - %m%n
运行这段代码你就可以直接将邮件发送出去了,而且是将日志记录以邮件的形式发出去。但由于有些邮件客户端的问题,可能会遇到类似下边的错误:
log4j:ERROR Error occured while sending e-mail notification.
javax.mail.MessagingException: 503 bad sequence of commands
由于本文只着重讲日志记录,所以这个错误这里不做讲解。
SocketAppender:这种Appender将发送LoggingEvent对象到一个远程的日志服务器,一般情况下是一个SocketNode,而该Appender本身拥有下边这些属性,该Appender是客户端操作的:
- 如果发送的是SocketNode,而且这种情况下,会实现日志记录的实时同步。换句话说,这些事件日志会在同一个时间戳进行记录,而且在这种情况下,有些日志会在本地被本地客户端保存下来。
- SocketAppender在使用过程中不需要使用Layout,它会直接把LoggingEvent对象序列化到服务器上
- 远程日志记录使用TCP协议,因此如果服务器是可达到的,那么事件日志最终将直接被送到服务器上
- 如果远程服务器出了问题,发送日志记录的请求将会暂停。尽管如此,如果当服务器恢复正常的时候,日志记录将会继续发送该请求,当能够继续连接服务器的时候,这种重连接会被一个connector线程启动。
- 事件日志将会被本地TCP实现进行缓冲存储。意思就是如果连接到服务器的速度很慢但是比日志记录产生日志的速度快的时候,该日志记录客户端将不会因为网速很慢而受影响;但是如果网速很慢比起日志记录客户端产生日志的速度都慢的时候,该日志记录客户端只能使用该网速的速录来进行日志记录。
- SocketAppender若不关联到任何类型,它将不会被connector线程内的垃圾回收器回收。而且connector线程仅仅会在服务器出问题的情况下才会存在,为了避免垃圾回收的问题,我们应该在编程过程中显示调用close()方法,
- 如果JVM主机的SocketAppender在它显示关闭或者被垃圾回收器回收之前退出,这种情况下可能使得数据通道里面的数据丢失,这是Windows系统的一个常见问题。所以为了避免数据丢失,最好是在该应用程序退出之前显示调用SocketAppender的close()方法或者直接调用LogManager.shutdown()方法。
SocketHubAppender:这个Appender和SocketAppender不同的地方是,该Appender是服务端操作的,该Appender一旦运行起来了就开始对外发送相关消息,无论有没有接收者,其相关特性和SocketAppender差不多,但是由于该Appender不负责输出信息,同样的也是不需要设置Layout的。
SyslogAppender:该Appender主要是负责向远程syslog服务器发送事件日志记录的
TelnetAppender:该Appender主要负责向一个只读端口发送时间日志,而且在整个过程是可以基于TCP/IP进行监控的。客户端可以通过telnet连接到该端口收取日志记录。
WriterAppender:WriterAppender可以将事件日志发送到用户选择的任何一个地方,输出类为基于Writer和OutputStream的相关设备。
ConsoleAppender:这种Appender会管理我们的日志消息,将日志事件记录到控制台以System.out或者System.err作为输出机制,它默认的输出为System.out;
FileAppender:该Appender可以将事件日志写入到一个文件里面,它支持java.io.Writer和Console的方式,在1.2的说明里面,将来可能会被WriterAppender和ConsoleAppender替代。
RollingFileAppender:这种Appender从FileAppender继承过来,大部分属性和FileAppender是类似的,当日志记录的文件到达文件容量的最大值的时候,会自动创建一个新的日志文件
ExternallyRolledFileAppender:该Appender用于监听某个端口号上边的“RollOver”消息【设置配置里面的Port】。
DailyRollingFileAppender:该Appender从FileAppender继承而来,能够按照一定的频度滚动日志记录文件。
[3]Log4j中的Layout:
Log4j中的Layout类似于JDK Logging里面的格式化输出,只是Log4j在输出过程将这些内容规范化了,从Log4j的整体结构上来讲,主要有五个子类的Layout
- HTMLLayout:输出为HTML的表格方式,如果没有指定编码格式的话,该输出的编码格式为UTF-8或UTF-16
- SimpleLayout:输出为最简单的格式,仅仅打印一个“Level名 - 日志记录信息”,例如:“DEBUG - Hello World”
- DataLayout:该类是一个抽象类,主要是通过可选项来定制输出,该类有一个子类TTCCLayout
|—TTCCLayout:TTCC的格式主要包含了时间、线程、分类、嵌套内容信息,这四个内容都是可以通过人为编程的方式进行关闭和打开的,时间格式依赖DateFormat类
176 [main] INFO org.apache.log4j.examples.Sort - Populating an array of 2 elements in reverse order.
225 [main] INFO org.apache.log4j.examples.SortAlgo - Entered the sort method.
262 [main] DEBUG org.apache.log4j.examples.SortAlgo.OUTER i=1 - Outer loop.
276 [main] DEBUG org.apache.log4j.examples.SortAlgo.SWAP i=1 j=0 - Swapping intArray[0] = 1 and intArray[1] = 0
290 [main] DEBUG org.apache.log4j.examples.SortAlgo.OUTER i=0 - Outer loop.
304 [main] INFO org.apache.log4j.examples.SortAlgo.DUMP - Dump of interger array:
该Layout的第一个字段是该程序执行的时间,以毫秒为单位,第二个字段主要输出的执行的线程,其次是相关日志记录的登记,第四个字段是该语句出现的位置以及相关类型。最后一个字段表示嵌套内容相关信息
【*:不要在不同的Appender里面使用同一个TTCCLayout实例,因为TTCCLayout是非线程安全的,最好的用法是一个Appender使用唯一的一个TTCCLayout实例。】
这里先看一段TTCCLayout的实例代码:
package org.susan.java.logging;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.apache.log4j.TTCCLayout;
/**
*针对TTCCLayout的一个简单应用
**/
public class TTCCLayoutDemo {
private static Logger logger = Logger.getLogger("TestTTCC");
private ConsoleAppender appender = null;
private TTCCLayout layout = null;
public TTCCLayoutDemo()
{
logger.setAdditivity(false);
// 初始化定义的TTCCLayout
layout = new TTCCLayout("yyyy-MM-dd");
// 初始化该Layout对应的Appender,这里使用的ConsoleAppender
appender = new ConsoleAppender(layout,"System.out");
// 将Appender添加到定义的Logger
logger.addAppender(appender);
}
public void computeSquareRoot(double number){
NDC.push(new Double(number).toString());
double sqrt = Math.sqrt(number);
logger.info("The sqrt value: " + sqrt);
NDC.pop();
}
public static void main(String args[]){
TTCCLayoutDemo demo = new TTCCLayoutDemo();
demo.computeSquareRoot(22);
demo.computeSquareRoot(44);
}
}
不需要log4j.properties文件,这里会得到下边的输出:
2009-09-17 [main] INFO TestTTCC 22.0 - The sqrt value: 4.69041575982343
2009-09-17 [main] INFO TestTTCC 44.0 - The sqrt value: 6.6332495807108
【*:这里需要注意的是输出和上边讲解遇到的TTCCLayout的输出不一样,因为我们在初始化构造TTCCLayout的时候使用的不同的参数的缘故。】
- XMLLayout:该Layout会按照XML格式输,其定义文件为log4j.dtd,该格式化输出输出的不是一个标准的良好格式的XML文档,一般格式如下:
version="1.0" ?>
DOCTYPE log4j:eventSet SYSTEM "log4j.dtd" []>
vertion="1.2" xmlns:log4j="http://jakarta.apache.org/log4j/">
&data;
该格式输出的编码一般是UTF-8或者UTF-16
- PatternLayout:这是Layout里面相对复杂的一个Layout这种情况下日志的格式是由模式字符串来进行设置的,主要依赖全局的format方法来格式化一个LoggingEvent然后通过模式转化将日志的格式输出,输出结果是需要依赖模式转换的
[4]模式转换:
模式转换经常使用在C语言的printf函数里面,模式转换就是定义一定的转化模式,然后将对应日志输出的值用转换模式进行匹配然后转换为字符串输出。在Log4j里面,所有的转换模式都是由一个%开始,然后跟着相对的模式符号,而且该模式可以使用在log4j.properties属性文件里面。
举个例子,如果使用了模式“%-5p [%t]: %m%n”模式,看下边代码段:
root.debug("Hello Debug!");
root.info("Hello Info!");
上边这段代码我们将会得到下边的输出:
DEBUG [main]: Hello Debug!
INFO [main]: Hello Info!
那么这些模式里面的模式表示什么意思呢?接下来看一张模式转换表:
模式参数 |
用法描述 |
举例 |
%c |
列出logger名字空间的名称 |
假设当前logger的名空间为“org.java.susan.logging” %c——org.java.susan.logging %20c——若名空间长度小于20的时候,则左边用空格填充 %-20c——若名空间长度小于20的时候,右边填充空白 %.40c——若名空间长度超过40就截去多余的字符 %20.40c——若名空间小于20,左边用空白填充,如果超过40就截去多余字符 %-20.40c——若名空间小于20,右边用空白填充,如果超过40就截去多余字符 %c{2}——susan.logging %c{4}——org.java.susan.logging |
%C |
列举出logger的类全名(包含包路径) |
假设当前类是org.java.susan.logging.PatternTester %C——org.java.susan.logging.PatternTester %C{1}——PatternTester |
%d |
显示日志记录的时间,{<日期格式>}使用ISO8601定义的日期格式 |
%d{yyyy/MM/dd HH:mm:ss,SSS}—— 2009/9/17 13:25:22,134【最后三位为毫秒】 %d{ABSOLUTE}——13:25:22,134 %d{DATE}——17 Sep 2009 13:25:22,134 %d{ISO8601}——2009-9-17 13:25:22,134 |
%F |
显示调用的logger的源文件名 |
%F——PatternTester.java |
%l |
输出日志事件发生的位置,包括类目录、发生线程,以及在代码中的行数 |
%l——PatternTester.fun(PatternTester.java:45) |
%L |
输出日志事件发生的代码行 |
45 |
%m |
显示输出消息 |
%m——该消息输出的主要目的是为了进行Debug操作 |
%M |
显示调用logger的方法名 |
%M——fun |
%n |
当前平台下的换行符 |
%n——表示换行 |
%p |
显示该日志的优先级 |
%p——INFO |
%r |
显示从程序启动时到记录该条日志时已经经过的毫秒 |
%r——1435 |
%t |
输出产生该日志事件的线程名 |
%t——PatternTester |
%x |
按NDC顺序输出日志 |
假设某线程调用顺序是MyDriver调用了org.java.susan.logging.PatternTester %c %x - %m%n——MyDriver - Call org.java.susan.logging.PatternTester - Log in PatterTester MyDriver - Return to MyDriver |
%X |
按MDC输出日志。通常用于多个客户端连接同一台服务器,方便服务器区分是哪个客户端访问留下来的日志。 |
%X{5}——(记录代号为5的客户端日志) |
%% |
显示一个百分号 |
%%——% |
[5]关于NDC和MDC:
NDC(Nested Diagnostic Context)和MDC(Mapped Diagnostic Context)是log4j中非常有用的两个类,它们用于存储应用程序的上下文信息,从而便于在log中使用这些上下文信息。
NDC采用了一个类似栈机制来push和pop上下文信息,每一个线程都独立地存储上下文信息。比如说一个servlet就可以针对每一个request创建对应的NDC,存储客户端地址等信息。当使用的时候,我们要尽可能确保在进入一个context的时候,把相关的信息使用NDC.push(message);在离开这个context的时候使用NDC.pop()将信息删除。另外由于设计上的一些问题,还需要保证在当前thread结束的时候使用NDC.remove()清除内存,否则会产生内存泄漏的问题。存储了上下文信息之后,我们就可以在log的时候将信息输出。
使用NDC的重要好处就是,当我们输出一些上下文信息的时候,不需要让logger去寻求这些信息,而只需要在适当的位置进行存储,然后再配置文件中修改PatternLayout。
MDC和NDC的用法类似,不同的是MDC内部使用了类似map的机制来存储相关信息,上下文信息也是每个线程独立存储,不同的是信息都是以它的key值存储在“map”中。相对应的方法,MDC.put(key,value);MDC.remove(key);MDC.get(key);在配置PatternLayout的时候使用:%x{key}来输出对应的value,同样的MDC也有一个MDCMatchFilter。【*:MDC是线程独立的,但是一个子线程会自动活得一个父线程MDC的拷贝】
3)配置相关以及属性文件log4j.properties:
单独把配置独立为一个小节是因为Log4j大部分是需要进行配置使用的,从上边邮件发送以及NT事件记录器的代码Demo可以知道其实仅仅需要在Log4j.properties文件里面配置一定的属性,开发过程就简化了很多,上边两端代码基本一模一样【NT日志记录器和Email日志记录Demo代码】,仅仅修改了配置文件。
[1]BasicConfigurator类:
BasicConfigurator类主要为Log4j提供一个基础的配置环境,通过调用BasicConfigurator.configure方法可以创建一个简单的Log4j环境,而且这个方法是通过硬编码直接将Appender设置为ConsoleAppender的,该配置的输出设置为PatternLayout类,模式为"%-4r [%t] %-5p %c %x - %m%n",日志记录的级别为Level.DEBUG,代码说明上边已经用到过了。
注意:如果不对Log4j进行任何配置的话,将会收到类似下边的警告:
log4j:WARN No appenders could be found for logger (org.susan.java.logging.Foo).
log4j:WARN Please initialize the log4j system properly.
如果我们通过配置log4j.properties配置文件的方式来进行配置,只需要进行下边的设置:
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
[2]log4j.properties文件:(参考链接:http://www.wangchao.net.cn/bbsdetail_60723.html)
根配置:
根日志记录器配置的语法为:[真正使用的时候,记得去掉开始的#,在properties文件里面#的含义标识行注释]
#log4j.rootLogger = [level], appenderName,appenderName,...
其中Level就是日志本身的优先级,log4j里面建议使用的级别为:ERROR,WARN,INFO,DEBUG
appenderName就是日志输出的地方,某一个Appender的类名
Appender配置:
配置日志输出目的Appender的语法为:
#log4j.appender.appenderName = className
#log4j.appender.appenderName.key = value
#log4j.appender.appenderName.key1 = value1
其中appenderName为定义一个Appender的名字、className为一个类全名
Layout配置:
布局配置格式的语法为:
#log4j.appender.appenderName.layout = className
#log4j.appender.appenderName.layout.key1 = value1
#log4j.appender.appenderName.layout.key2 = value2
关于Log4j中使用布局打印的模式如下:
#%p 输出优先级,DEBUG,INFO,WARN,ERROR,FATAL
#%r 输出自应用启动到输出该log信息耗费的毫秒
#%c 输出所属的类,通常就是在所在类的全名
#%t 输出产生该日志事件的线程名
#%n 输出一个回车换行,Windows为“/r/n”,Unix平台为“/n”
#%d 输出日志的时间点的日期或者时间,格式为ISO8601,也可以使用%d{yyyy MM dd}格式化输出
#%l 输出日志发生的位置,包括类名、线程以及代码中的行
【例:配置一个名为myConsole的ConsoleAppender,其输出Layout为TTCCLayout】
log4j.appender.myConsole = org.apache.log4j.ConsoleAppender
log4j.appender.myConsole.layout = org.apache.log4j.TTCCLayout
【例:配置一个TTCCLayout的具体属性】
log4j.appender.myConsole.layout.ThreadPrinting=false
log4j.appender.myConsole.layout.ContextPrinting=false
log4j.appender.myConsole.layout.CategoryPrefixing=false
log4j.appender.myConsole.layout.DateFormat=RELATIVE
【例:配置一个自定义名称的Logger】
log4j.logger.org.susan.java.logging.mylogger=DEBUG,myConsole
这种情况下,可以使用Logger logger = Logger.getLogger("org.susan.java.logging.mylogger");来获取该日志记录,而该日志记录的输出输出到myConsole这个Appender
【例:配置一个FileLayout】
log4j.appender.myConsole.layout = org.apache.log4j.PatternLayout
log4j.appender.myConsole.layout.conversionPattern=%p - %m%n
【例:配置一个SMTP的邮件记录】
log4j.logger.EmailLogger = WARN,SMTP
log4j.appender.SMTP=org.apache.log4j.net.SMTPAppender
log4j.appender.SMTP.SMTPHost=smtp.126.com
log4j.appender.SMTP.Subject=ErrorLog
log4j.appender.SMTP.layout=org.apache.log4j.SimpleLayout
【例:NT事件查看器配置】
log4j.logger.NTLogger = FATAL,NTEVENT
log4j.appender.NTEVENT=org.apache.log4j.nt.NTEventLogAppender
log4j.appender.NTEVENT.Source=org.susan.java.logging.NTEventTester
log4j.appender.NTEVENT.layout=org.apache.log4j.PatternLayout
log4j.appender.NTEVENT.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1};%L - %m%n
【例:Rolling文件配置】
log4j.logger.RollingLogger = FATAL,ROLLING_FILE
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=ERROR
log4j.appender.ROLLING_FILE.File=rolling.log
log4j.appender.ROLLING_FILE.Append=true
log4j.appender.ROLLING_FILE.MaxFileSize=10KB
log4j.appender.ROLLING_FILE.MaxBackupIndex=1
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
【例:Socket相关配置】
log4j.logger.SocketLogger = INFO,SOCKET
log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost=localhost
log4j.appender.SOCKET.Port=5001
log4j.appender.SOCKET.LocationInfo=true
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.
SOCKET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n
【例:JDBC配置】
log4j.logger.JDBCLogger = INFO,DATABASE
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/db_rect
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=root
log4j.appender.DATABASE.password=********
log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES ('[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n')
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
【例:DailyRolling日志记录配置】
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File=SampleMessages.log4j
log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j'
log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout
ii.Commons Logging Framework:
1)JCL基本概念[介绍版本为1.1]:
Commons Logging Framework又称为JCL(Java Common Logging),该日志记录框架和Log4j以及Java Logging(JDK 1.4 Logging Framework)存在本质区别。前两个日志记录器本身提供了详细的实现,都有一个属于自己的体系结构,而JCL本身没有。JCL可以真正称为一个日志框架,因为它提供的是一个日志接口,而且是一个轻量级的不依赖实现的日志记录的抽象接口工具包,它允许任何开发者使用工具在该抽象框架下边进行日志记录的实现层的开发。而它本身只是一个轻量级的抽象框架:
官方下载地址:
http://apache.freelamp.com/commons/logging/binaries/commons-logging-1.1.1-bin.zip
因为JCL本身的类比较少,先看看整个抽象框架的包结构:[I]标识接口,[A]标识抽象类,[C]标识具体类,[E]标识异常
org.apache.commons.logging
|—[I]Log
|—[C]LogFactory
|—[C]LogSource
|—[E]LogConfigurationException
org.apache.commons.logging.impl
|—[C]AvalonLogger
|—[C]Jdk13LumberjackLogger
|—[C]Jdk14Logger
|—[C]Log4JLogger
|—[C]LogFactoryImpl
|—[C]LogKitLogger
|—[C]NoOpLog
|—[C]ServletContextCleaner
|—[C]SimpleLog
|—[C]WeakHashtable
- Log接口:
实现该接口的实例可以被LogFactory顺利创建和初始化,如果某一个日志记录想要初始化必须调用一个带String单参数的构造函数,传入的语意为该日志记录器的名称
Log接口定义了日志的几个登记,所有实现该接口的类都需要遵循这几个等级:
trace、debug、info、warn、error、fatal
- LogFactory为一个抽象类,使用工厂模式,可以创建一个实现了Log接口的日志记录器实例,而且该记录器实现是集成了JavaAPI和JAXP
【关键:LogFactory的实现是基于SAXParserFactory和DocumentBuilderFactory的,所以在使用的时候有可能还会用到Apache Xerces[一个可以解析XML的库]】
- LogSource类【Deprecated】:
该类是原版本的创建Log实例的类,现在已经被LogFactory替代了。当LogFactory创建某个Log实例的时候,应用程序可以通过调用方法makeNewLogInstance去实例化某个类或者针对该类的实现类进行配置工作。默认情况下,如果调用方法getInstance()将会使用下列算法:
[1]如果Log4J是配置好了的,那么LogFactory会创建一个org.apache.commons.logging.impl.Log4JLogger实例
[2]如果JDK是1.4或者以上的版本,LogFactory就会创建一个org.apache.commons.logging.impl.Jdk14Logger实例
[3]如过两个都没有满足,就返回一个org.apache.commons.logging.impl.NoOpLog实例
上边是默认的配置和默认的创建实例的行为,但是我们可以通过两种方法修改相关内容
[1]启动命令提示行,在进行java编译的时候设置自定义的org.apache.commons.logging.Log的实现类的名称
[2]在运行时调用LogSource.setLogImplementation()
- LogConfigurationException类:
当使用LogFactory创建实现了Log类接口的实例的时候,如果创建失败就会抛出该异常
- AvalonLogger类:
该类有两种用途:
[1]该类的实例可以通过AvalonLogger(Logger)方法来进行构造,在这种情况下,该类在Logger外层进行了一层简单的封装,这种用法最大的用途在于使用一个属性设置器的时候。
[2]方法setDefaultLogger(org.apache.avalon.framework.logger.Logger)的属性调用来设置Avalon类的关联
这里插入Avalon框架的一段介绍:Apache的Avalon是一个包括核心框架、工具、组件和容器的面向组件编程(COP)的完整开发平台。通过使用关键设计模式,如反向控制模式(IoC)和分离考虑模(SoC),Avalon实现了传统OOP框架的一些优点: 1.没有执行锁 2.组件之间低耦合 3.管理组件生命周期 4.配置管理和易用的API 5.组件元数据框架和工具 6.服务相关的管理独立的、J2EE或Web环境的嵌入式容器 在COP方面,可重用的组件能够被组合到容器中,以提供应用程序模块。模块可以依次使用来创建你所需要的,从客户桌面应用程序,到FTP服务器,到Web服务,等等。Avalon提供各种基本组件和缺省的应用程序模块,帮助你快速的建立你自己的应用程序解决方案。
- Jdk13LumberjackLogger:实现了接口org.apache.commons.logging.Log,封装了Lumberjack实现部分,主要用于JDK 1.4之前实现Java的日志记录,Lumberjack项目是用来进行Java日志记录的老版本的日志API
- Jdk14Logger:实现了接口org.apache.commons.logging.Log,用来封装了上边我们讲到的JDK 1.4 Logging Framework
- Log4JLogger:实现接口org.apache.commons.logging.Log,用来封装了Log4j版本1.2里面的Logger实例,初始化该实例的配置的时候应该使用通常的初始化方式。
该类不能用于Log4j 1.3,主要原因在于:[1]1.2版本的Logger使用的是Priority参数而不是我们介绍的Level参数;[2]Level类是继承于Priority的,在1.3版本里面,需要修改相关优先级,但是不是继承于Priority的,所以不兼容1.3版本,具体原因是否如此我不太清楚。
- LogFactoryImpl类:LogFactory的子类,通过一定的算法动态选择应该使用哪种日志记录器,主要对底层的不同的日志实现类进行简单的封装
[1]使用工厂配置属性org.apache.commons.logging.Log用来标识使用的实现类
[2]使用org.apache.commons.logging.Log系统属性用来标识使用的实现类
[3]如果Log4J是配置好了的,那么LogFactory会创建一个org.apache.commons.logging.impl.Log4JLogger实例
[4]如果JDK是1.4或者以上的版本,LogFactory就会创建一个org.apache.commons.logging.impl.Jdk14Logger实例
[5]如过两个都没有满足,就返回一个org.apache.commons.logging.impl.SimpleLog实例
不仅仅如此,该类还有一个方法可以提供反向的关联,使用Log实现类的方法setLogFactory(),传入一个参数LogFactory,这种方法调用过后就可以修改实现类对应的Factory的关联项,在整个程序运行过程中,Factory将会记录下来所有创建过的Logger实例对象,当再次调用getInstance()方法的时候就直接从记录里面激活该实例。
【我仔细思考了以下,这个地方应该使用了Logger的“池化”技术,使用工厂虽然可以反复进行实例的构造,但是使用的是Logger的name作为标识,从设计上考虑,Logger的名字一般情况下都是某个类的全名,从整个系统级别讲,如果OO设计设计得不错的话,每一个类如果需要进行日志记录的话最好使用单个日志记录器,这样可以节省系统开销。】
- LogKitLogger类:实现了接口org.apache.commons.logging.Log,该类封装了avalon-logkit日志系统,仅仅将LogKit留给用户自己进行配置。
- NoOpLog类:该类将会抛出所有的相关异常信息,没有任何系统属性配置的支持,一般情况下不使用该类。
- SimpleLog类:简单实现了org.apache.commons.logging.Log接口,用来记录所有可用的日志信息,而且把所有定义好的Logger输出到System.err,下边的一些系统属性可以用来配置该Logger的一些属性:
[1]org.apache.commons.logging.simplelog.defaultlog——默认的SimpleLog的等级参数,必须是以下的值的集合(trace,debug,info,warn,error,fatal),如果没有设置默认为info
[2]org.apache.commons.logging.simplelog.log.xxxxx——定义某个名称为xxxxx实例的日志器的等级,同上值必须是一个Level的集合
[3]org.apache.commons.logging.simplelog.showlogname——如果为true在日志记录的时候需要输出name属性,如果false的话就不输出Logger的名称,默认为false
[4]org.apache.commons.logging.simplelog.showShortLogname——和上边参数一样,唯一的区别是该项是类名,而上边的参数是输出的类全名,还有不同的是默认值为true
[5]org.apache.commons.logging.simplelog.showdatetime——为true就输出时间,如果为false就不输出时间,默认是false
[6]org.apache.commons.logging.simplelog.dateTimeFormat——设置时间的格式,该格式应该是java.text.SimpleDateFormat可以解析的时间合法格式
这种配置需要检测类加载器在路径里面是否可以找到simplelog.properties的属性文件用来配置以上的选项,或者直接在java命令后边带上相关的参数
- ServletContextCleaner类和WeakHashtable类在此不做讲解,需要了解的可以去查询该类的API,而且我没有用到过这两个类,所以也没有去了解这两个类到底做了些什么,不好意思。
2)如何配置Log4j实现