log4j的简介以及使用(log4j-1.2.14)

一、log4j的类图

 

log4j有三个主要的控件:Logger、Appender、Layout,这三者一起使用可以使开发者根据日志的类型和级别记录日志。

 

同时,log4j是必须配合配置文件log4j.properties一起使用,因为要使用log4j,就必须有log4j.properties配置文件,并且配置文件的内容要正确。关于log4j.properties文件的内容会在后文讨论。

 

log4j类图如下所示:


log4j的简介以及使用(log4j-1.2.14)

  • Logger - 日志写出器,供程序员输出日志信息
  • Appender - 日志目的地,把格式化好的日志信息输出到指定的地方去
    • ConsoleAppender - 目的地为控制台的Appender
    • FileAppender - 目的地为文件的Appender
    • RollingFileAppender - 目的地为大小受限的文件的Appender,文件大小达到指定尺寸时产生一个新的日志文件,文件名称上会自动添加数字序号。
    • DailyRollingFileAppender-每天都产生一个日志文件的Appender
    • WriterAppender-将日志信息以流的格式发送到任意指定的地方的Appender
  • Layout - 日志格式化器,用来把程序员的日志信息格式化成字符串
    • PatternLayout - 用指定的pattern格式化日志的布局
    • HTMLLayout -以HTML表格形式格式化日志布局
    • SimpleLayout-以包含日志信息的级别和信息字符串格式化日志布局
    • TTCCLayout-以包含日志产生的时间、线程、类别等信息的方法格式化日志布局

二、log4j的等级

 

每个logger都有一个日志级别,用来控制日志的输出。未分配级别的logger将自动继承它最近的父logger的日志级别。

 

Logger的由低到高级别如下:


ALL<DEBUG<INFO<WARN<ERROR<FATAL<OFF

 

Logger的级别越低,输出的日志越详细。


三、Logger(日志写出器)

 

(1)Logger简介

Logger是日志写出器,用来取代System.out或者System.err的日志写出器,用来供程序员输出日志信息。

 

Logger由一个String类的名字识别,logger的名字是大小写敏感的,且名字之间具有继承的关系,子名有父名作为前缀,用点号.分隔。如:x.y是x.y.z的父亲。

根logger (root logger)是所有logger的祖先,它具有如下属性:

 

1) 它总是存在的;

2) 它不可以通过名字获得。

可以通过调用

 

public static Logger Logger.getRootLogger()

 获得root logger。

 

可以通过调用

 

public static Logger Logger.getLogger(String name)
 

或者

 

public static Logger Logger.getLogger(Class clazz)
 

 

获得(或者创建)一个named logger。后者相当于调用Logger.getLogger(clazz.getName())。


在某对象中,用该对象所属的类为参数,调用Logger.getLogger(Class clazz)以获得logger被认为是目前所知的最理智的命名logger的方法。

 

用同名参数调用Logger.getLogger(Class clazz)将返回同一个logger对象。Logger的创建可以按照任意顺序,父logger可以后于子logger被创建。log4j将自动维护logger的继承树。


四、Appender(日志目的地,日志输出终端)

 

如上文所示,Appender是日志输出终端,通过该类,可以把日志输出到控制台(ConsoleAppender)、文件(FileAppender、RollingFileAppender、DailyRollingFileAppender)、流的格式(WriterAppender),还可以是邮件,数据库等。

 

 

可以使用Logger.addAppender(Appender app)为logger增加一个appender,可以使用Logger.removeAppender(Appender app)为logger移除一个appender。

 

默认情况下,logger的additive标志被设置为true,表示子Logger将继承父Logger的所有Appenders。也可以被重新设置,表示子logger将不再继承父logger的appenders。

 
rootlogger拥有目标为system.out的consoleAppender,故默认情况下,所有的logger都将继承该appender。

 

每个Logger都可以拥有一个或者多个Appender,每个Appender表示一个日志的输出目的地,比如console或者某个文件。

 

(1)ConsoleAppender

 

可以使用ConsoleAppender对象把日志输出到控制台。每个ConsoleAppender都有一个target,表示它的输出目的地。它可以是System.out,标准输出设备(缓冲显示屏);或者是System.err,标准错误设备(不缓冲显示屏)。ConsoleAppender的使用方法参考如下API:

 

// Log4j APIs : class ConsoleAppender extends WriterAppender


      


// 构造方法,使用一个Layout对象构造一个ConsoleAppender对象 


// 默认情况下,ConsoleAppender的target是System.out 


public ConsoleAppender(Layout layout);





// 构造方法,使用一个Layout对象和一个target字符串构造ConsoleAppender对象 


// target的可能取值为ConsoleAppender.SYSTEM_OUT和ConsoleAppender.SYSTEM_ERR 


public ConsoleAppender(Layout layout, String target);


(2)FileAppender

 

可以使用FileAppender对象把日志输出到一个指定的日志文件中去。使用方法可以参考如下的API:

 

// Log4j APIs : class FileAppender extends WriterAppender


      


// 构造方法,使用一个Layout对象和日志文件名构造一个FileAppender对象 


public FileAppender(Layout layout, String filename)


throws IOException;


public FileAppender(Layout layout, String filename, boolean append)


throws IOException;

 

(3)RollingFileAppender

 

可以使用FileAppender的子类RollingFileAppender对象,把日志输出到一个指定的日志文件中。不同的是该日志文件的大小受到限制,当日志内容超出最大的尺寸时,该文件将向上滚动(最老的日志被擦除)。还可以在该类对象中指定为日志文件做多少个备份。具体使用方法参考如下 API:

 

// Log4j APIs : class RollingFileAppender extends FileAppender


      


// 构造方法,使用一个Layout对象和日志文件名构造一个RollingFileAppender对象 


public RollingFileAppender(Layout layout, String filename)


throws IOException;


public RollingFileAppender(Layout layout, String filename, boolean append)


throws IOException;





// 获得和设置日志备份文件的个数 


public int getMaxBackupIndex();


public void setMaxBackupIndex(int index);





// 获得和设置滚动日志文件的最大尺寸 


public long getMaximumFileSize();


public void setMaximumFileSize(long size);
 

(4)DailyRollingFileAppender

 

可以使用FileApender的子类DailyRollingFileAppender对象,每天产生一个日志文件,将日志输出到该文件中。记录日志的周期(Rolling)的调度是由DatePattern选项来确定的。DatePattern的pattern(模式)必须跟SimpleDateFormat 的中的pattern的约定一样。不过,DatePattern的pattern跟SimpleDateFormat中的pattern的不同之处是,DatePattern中的pattern的内容在模式前面必须加上一对单引号,单引号的内容为一个点,即必须加上前缀--'.' 。这是作为日志文件名的后缀的。

 

如:如果设置DailyRollingFileAppender中的文件名为/foo/bar.log,并且其DatePattern的值为'.'yyyy-MM-dd。那么在2001-02-16的半夜12点之后,日志文件/foo/bar.log会被复制成/foo/bar.log.2001-02-16,而/foo/bar.log日志文件会向上滚动(最老的日志被擦除),继续写第二天,即2001-02-17的日志内容。

 

日志文件的向上滚动的周期可以是一个月(monthly),一周(monthly),半天(half-daily),一天(daily),一个小时(hourly),一分钟(minutely)。

DatePattern

Rollover Schedule

例子

'.'yyyy-MM

在每个月的月初向上滚动

2002-5-31 的半夜12 点之后,日志文件/foo/bar.log 会被复制成/foo/bar.log.2002-05 ,然后20026 月的日志信息会被输出到/foo/bar.log 文件中,直到下个月时日志文件向上滚动,擦除老的日志。

'.'yyyy-ww

在每一周的第一天向上滚动。每一周的第一天跟当地时间有关系。

假设每一周的第一天是周日,那么在2002-6-9 的星期六半夜12 点,日志文件/foo/bar.log 会被复制成/foo /bar.log.2002-23 。而2002 年的第24 周的日志内容会被输出到/foo/bar.log 文件中,直到下一周时日志文件向上滚动,擦除老 的日志。

'.'yyyy-MM-dd

在每天的半夜12 点向上滚动。

2002-03-08 的半夜12 点,日志文件/foo/bar.log 会被复制成/foo/bar.log.2001-03-08 ,而/2002-03-09 日的日志内容会被输出到foo/bar.log 文件中,直到第二天半夜12 点会向上滚动( 最老的日志被擦除)

'.'yyyy-MM-dd-a

在每天中午和半夜12 点向上滚动。

2002-03-09 的中午,日志文件/foo/bar.log 会被复制成/foo/bar.log.2002-03-09-AM 。直到当天半夜12 点日志文件向上滚动( 老的日志被擦除) 之前,9 日下午的日志内容会被输出到/foo/bar.log 文件中。

'.'yyyy-MM-dd-HH

在每个小时的最开始时向上滚动

大约在2002-03-09 日的11:00.00 时,日志文件/foo/bar.log 会被复制成/foo /bar.log.2002-03-09-10 。而直到下个小时,日志文件向上滚动( 老的日志被擦除) 之前,9 日的第11 个小时的日志内容会被输出到 /foo/bar.log 文件中。

'.'yyyy-MM-dd-HH-mm

在每一分钟的最开始时向上滚动。

2001-03-09 日的的大约11:23,000 时,日志文件/foo/bar.log 会被复制成/foo /bar.log.2001-03-09-10-22 。而直到下一个分钟,日志文件向上滚动( 老的值日被擦除) 之前,391123 分的日志内容会被 输出到/foo/bar.log 文件中。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

// 构造方法,使用一个Layout对象和日志文件名构造一个RollingFileAppender对象以及一个datePattern字符串


public DailyRollingFileAppender (Layout layout,String filename,String datePattern)throws IOException;


//默认构造函数什么都没做,可以通过setLaoout(),setName(),setDatePattern()方法设置相应的Layout对象、日志文件

//名、dataPattern字符串

public DailyRollingFileAppender();


//设置datePattern

public void setDatePattern(String pattern);
 

五、Layout(日志格式化器)

 

每个Appender都和一个Layout相联系;Layout的任务是格式化用户的logging request,Appender的任务是把Layout格式化好的输出内容送往指定的目的地。Layout是log4j的输出布局模式。

 

(1)PatternLayout

 

PatternLayout可以灵活的指定布局模式。这个布局模式可以通过conversionPattern选项来指定。

Log4j采用类似C语言中的printf函数的打印格式格式化日志信息。打印参数如下:

 

%m :输出代码中指定的消息。


%M :输出发生日志事件的方法名。注意,生成这些信息会很慢,因此,除非执行速度不是问题,否则最好避免使用。


%p :输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL。


%r :输入自应用启动到输出该log信息耗费的毫秒数。


%c :输出所属的类目,通常就是所在类的全名。后面可以跟着精确度说明符。这个精确度说明使用大括号括起来的,括号中的值是数字。如果有精确度说明符,就只会打印出从右边开始的相应精确度的所属类名。比如,类名为“org.apache.xxx”,如果pattern的值为"%c{2}",则只会输出“apache.xxx”。

 

%C :通常会输出调用分配了logging request的类全限定名,后面可以跟着精确度说明符。这个精确度说明使用大括号括起来的,括号中的值是数字。如果有精确度说明符,就只会打印出从右边开始的相应精确度的所属类名。比如,类名为:

"org.apache.xyz.SomeClass",其pattern的值为"%C{1} ",则只会打印出"SomeClass"。注意:生成调用者类信息是很慢的,因此,除非执行速度不是问题,否则最好避免使用。


%t :输出产生该日志线程的线程名。


%n :输出一个回车换行符。Windows平台为“\r\n”,UNIX为“\n”。


%d :输出发生日志事件的日期或时间,默认格式为ISO8601,也可以在其后指定格式,其格式跟SimpleDateFormat中pattern的格式是一样的。比如:%d{yyyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921。推荐使用log4j自己的日期格式器,log4j自己的日期格式器有三种,分别是"ABSOLUTE", "DATE" 和 "ISO8601",分别对应于AbsoluteTimeDateFormat, DateTimeDateFormat 和ISO8601DateFormat类。比如
%d{ISO8601} or %d{ABSOLUTE}。这些专门的日期格式化运行起来比SimpleDateFormat要有好的多。

 

%F :输出logging request发生时的文件名。注意:生成调用者日志事件发生的位置信息非常慢,因此,除非执行速度不是问题,否则最好避免使用。


%l :输出日志事件发生的位置,包括类名、线程名,以及所在代码的行数。这些信息都非常有用,但是生成这些信息非常慢,因此,除非执行速度不是问题,否则最好避免使用。

 

%L :输出日志事件发生的行数。注意,生成这些信息会很慢,因此,除非执行速度不是问题,否则最好避免使用。

 

%x :输出跟日志事件发生的线程相关的NDC (nested diagnostic context)。

 

%X :输出跟日志事件发生的线程相关的MDC (mapped diagnostic context)。这个参数的后面必须有一个大括号,括号中的值为map的key值,比如%X{clientNumber},其中,clientNumber就是key,这个Key在MDC的值会被输出来。

 

 

 

如在log4j.properties中设置layout的格式化信息为:

 

log4j.appender.stdout.layout.ConversionPattern= [QC] %p [%t] %C.%M(%L) | %m%n
 

[QC]是log信息的开头,可以为任意字符,一般为项目简称。

 

其中,ConversionPattern中每个转义符都以%开头,后面跟着可选的可视化修饰符,如一对中括号[],以及转义符,如m。

 

假设log4j的layout被设置为PatternLayout,并且其conversionPattern(转义模板)的值为“%-5p [%t]: %m%n ”。则下面的语句:

 

   Category root = Category.getRoot();
   root.debug("Message 1");
   root.warn("Message 2");
 

则输出的内容为:

 

   DEBUG [main]: Message 1
   WARN  [main]: Message 2
 

注意,在这里,在文本和转义符之间没有明显的分隔符,pattern解析器会在读取到一个转义符的最后的一个转义说明符时进行解析。在上面的例子中的转义说明符"%-5p"表示日志事件的优先级必须向左对齐5个字符的宽度。已知的转义符如上文所示。

 

而修饰符还可以修改输出内容的对齐方式,最大字段宽度,和最小字段宽度等。这个可选的修饰符是放在%和转义符之间的。

 

第一个可选修饰符是左对齐,是一个减号字符(-)。

 

第二个可选修饰符是最小字段宽度修饰符,用小数表示要输出的字符串的最小数量。如果输出内容比最小字段宽度的数量还要小,则会在左边或者右边填补,知道达到最小字段宽度的数量。默认的填补方式是在左边填补(向右对齐),但是你可以指定右边填补(向左对齐),填补的内容是空格。如果输出的字符串内容比最小字段宽度要大,则会自动调整适应数据。这些不会被截断。

 

第三个可选修饰符是最大字段宽度修饰符,在小数点后面来指定。如果要输出的字符串比最大字段宽度还要大,则额外的数据会从字符串的开头被移除,而不是从字符串的结尾移除。比如,最大字段宽度是8,而字符串有10个,则这个字符串前面两个字符会被丢弃。这跟C语言从字符串末尾丢弃是刚好相反的。

 

下面是几个格式化修饰符的例子:

 

格式化修饰符 左对齐 最小宽度 最大宽度 注释
%20c false 20 如果类名的字符串长度小于20,则会在左边补齐空格
%-20c true 20 如果类名的字符串长度小于20,则会在右边补齐空格
%.30c NA 30 如果类名的字符串长度超过30,则会从字符串的开头截去相应的超出长度。
%20.30c false 20 30 如果类名的字符串长度小于20,则会在左边补齐空格。然而,如果类名的字符串长度超过30,则会从字符串的开头截去相应的超出长度。
%-20.30c true 20 30 如果类名的字符串长度小于20,则会在右边补齐空格。然而,如果类名的字符长度30,则会从字符串的开头截去相应的超出的长度。

 

 

 

 

 

 

 

 

 

 

 

 

下面是一些pattern例子:

 

%r [%t] %-5p %c %x - %m%n
 

这个跟TTCCLayout的布局是一样的。

 

%-6r [%15.15t] %-5p %30.30c %x - %m%n
 

除了如果相对时间小于6个数字就右边补齐,线程名字如果小于15个字符就右边补齐,如果大于15个字符就从线程名字前面截去多出的字符,类名如果少于30个字符就左补齐,如果大于30个字符就从类名前面截去多出的字符之外,其他的跟TTCLayout是一样的。

 

(2)HTMLLayout

 

以HTML表格形式布局。使用这个布局必须设置html的编码格式为'UTF-8"或者"UTF-16",否则的话,会因为无法包含ASCII码而导致日志文件崩溃。

 

(3)SimpleLayout

 

包含日志信息的级别和信息字符串,中间用“-”隔开。

 

比如:

DEBUG - Hello world

 

(4)TTCCLayout

 

包含日志产生的时间、线程、类别等信息。

 

下面是使用了RelativeTimeDateFormat格式化时间的一个TTCCLayout的输出:

 

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:
317 [main] INFO  org.apache.log4j.examples.SortAlgo.DUMP - Element [0] = 0
331 [main] INFO  org.apache.log4j.examples.SortAlgo.DUMP - Element [1] = 1
343 [main] INFO  org.apache.log4j.examples.Sort - The next log statement should be an error message.
346 [main] ERROR org.apache.log4j.examples.SortAlgo.DUMP - Tried to dump an uninitialized array.
        at org.apache.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
        at org.apache.log4j.examples.Sort.main(Sort.java:64)
467 [main] INFO  org.apache.log4j.examples.Sort - Exiting main method.
 

其中,第一个字段表示的是自应用启动到输出该log信息耗费的毫秒数。第二个字段是产生该日志线程的线程名。第三个字段是日志的级别(优先级)。第四个字段是发生日志的类名,第五个字段(在'-'之前)是表示内嵌的诊断信息的上下文(NDC)。可以注意到,这个内嵌的诊断信息上下文可能为空。在'-'之后的内容就是诊断信息的内容。

 

注意:不要在不同的Appenders中使用相同的TTCCLayout实例,TTCCLayout这样使用的时候是非线程安全的。然而,如果只在一个Appenders中使用一个TTCCLayout实例的话是相当安全的。

 

其构造方法如下所示:

 

//在本地时区中,使用RelativeTimeDateFormat作为时间格式化器,实例化一个TTCCLayout对象。
public TTCCLayout();

//在本地时区中,实例化一个TTCCLayout对象,时间的格式根据dateFormatType字符串来决定。
public TTCCLayout(String dateFormatType);


六、Log4j配置文件

 

在实际使用中,Log4j一般是通过配置文件配置使用的。配置文件有两种,Java properties和XML文件。一般都选用properties文件来配置,因为简洁易读。下面只介绍Java properties的配置方式。

 

对Log4j的配置就是对rootLogger和子Logger的配置。主要的配置项为:rootLogger、输出终端、输出布局模式。
 
所有的配置项都必须以log4j开头。

 

下面给出一个配置文件的示例,用以说明配置的方法。

 

### logger的配置 ###
#配置根logger
log4j.rootLogger=INFO,stdout,file
#配置子logger:com.docwar(在com.docwar包中类的日志在没有指定子logger名的情况下使用这个logger)
log4j.logger.com.docwar=ERROR,file
#配置子logger:com.docwar.test(在com.docwar.test包中类的日志在没有指定子logger名的情况下使用这个logger)
log4j.logger.com.docwar.test=ERROR,file1,stdout

### direct log messages to stdout ### (标准的终端输出)
#控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#自定义输出布局
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#输出的格式
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file test.log ### (输入到文件ttt.log的配置)
#输出到滚动文件
log4j.appender.file=org.apache.log4j.RollingFileAppender
#输出文件最大为10M
log4j.appender.file.MaxFileSize=10MB
#输出文件最大序号为10
log4j.appender.file.MaxBackupIndex=10
#输出文件路径
log4j.appender.file.File=C:/test.log
#自定义输出布局
log4j.appender.file.layout=org.apache.log4j.PatternLayout
#输出格式
log4j.appender.file.layout.ConversionPattern=%d %-5p [%t] (%13F:%L) %3x - %m%n

### direct messages to file test1.log ### (输入到文件test1.log的配置)
#输出到滚动文件
log4j.appender.file1=org.apache.log4j.RollingFileAppender
#输出文件最大为10M
log4j.appender.file1.MaxFileSize=10MB
#输出文件最大序号为10
log4j.appender.file1.MaxBackupIndex=10
#输出文件路径
log4j.appender.file1.File=C:/test1.log
#自定义输出布局
log4j.appender.file1.layout=org.apache.log4j.PatternLayout
#输出格式
log4j.appender.file1.layout.ConversionPattern=%d %-5p [%t] (%13F:%L) %3x - %m%n

 

Logger的配置语法为:级别,输入终端1,输出终端2,…
根Logger的配置项为:log4j.rootLogger
 
子logger的配置项为:log4j.logger.<子logger名>

 

在上面的例子中,配置了一个根Logger和两个子logger,名称为:com.docwar和com.docwar.test。通过该名称,利用Logger.getLogger(String name)可以获取这两个logger对象。

 

子logger的名字一般都以包名来配置,这样当在程序通过类名获取logger的对象就是与本类包名相同的子logger,这样可以方便控制某个包下面logger的输出。

 

但是一般不建议设置子logger,一个通用的根Logger足够用了。由于继承关系,并且可以在任何地方通过子 logger的名字获取logger对象,这样容易导致混乱,比如在com.docwar包下的类直接获取了名为com.docwar.test子logger的对象,这是不合理的(虽然日志可以打印出来),因为如果直接在类中用Logger.getLogger(Class clazz)方式来获取Logger对象,则获取到的应该是名为org.lavasoft的logger对象,而不会得到名为 org.lavasoft.test的logger对象。

 

以上的配置文件纯粹是为了说明问题才那么写的。

 

在上面的例子中,根logger配置了两个输出终端,分别是stdout以及file。而在输出终端stdout中,指定Appender是控制台--ConsoleAppender,并且指定了相应的layout为PatternLayout,并且在下面指定了layout中的ConversionPattern表达式。

 

在输出终端file中,指定Appender为RollingFileAppender,将日志输入保存为限制大小的文件格式,并且文件路径和名称为:C:/test.log,指定了备份的文件个数为10。其他的也是差不多这样。

 

注:本文研究的Log4j版本为1.2.14

 

文章参考了以下几篇文章:

 

http://lavasoft.blog.51cto.com/62575/26134


http://heavyz.sourceforge.net/homepage/homepage_zh/comp/notes/log4j.html

 

http://www.blogjava.net/kit-soft/archive/2009/08/28/292977.html

 

http://logging.apache.org/log4j/1.2/apidocs/index.html