Copyright© 2000-2004 The Apache Software Foundation. 版权所有。Log4j软件是在遵守Apache Software License 1.1版的条例下发行的,Apache Software License的复制件被包括在log4j发布的LICENSE.txt文件里。这个简短手册也借用了The complete log4j manual 里的一些内容,The complete log4j manual包含最新的更为详尽的信息。 The complete log4j manual
摘要
这个文档资料描述了log4j API,它的独特的特性和设计原理。Log4j是由许多作者共同参与的开放源代码项目。它允许开发人员以任意的精细程度控制哪些日志说明被输出。通过使用外部的配置文件,可以在运行时配置它。最好的是,log4j 开发包很容易上手。注意,它也可能会使一些开发人员着迷。
简 介
几乎每个大的应用程序都有它自己的日志和跟踪程序的API。顺应这一规则,E.U. SEMPER 项目组决定编写它自己的程序跟踪API(tracing API)。这开始于1996年早期。经过无数的工作,更改和性能加强,这个API终于成为一个十分受欢迎的Java日志软件包,那就是log4j。这个软件包的发行遵守open source 动议认证的Apache Software License 。最新的log4j版本包括全部的源代码,类文件和文档资料,可以在 http://logging.apache.org/log4j/ 找到它们。另外,log4j已经被转换成 C, C++, C#, Perl, Python, Ruby, 和 Eiffel 语言。
把log statements插入到你的代码中是一种排错的低技能办法。这也许是唯一的方法,因为排错工具并不总是可以被使用或者适用于你的程序。对于多线程的应用程序和多数发行的应用程序,通常就是这样的情形。
经验告诉我们logging是开发过程中重要的一环。它具有多种优点。首先,它能精确地提供运行时的上下文(context )。一旦在程序中加入了Log 代码,它就能自动的生成并输出logging信息而不需要人为的干预。另外,log信息的输出可以被保存到一个固定的地方,以备以后研究。除了在开发过程中发挥它的作用外,一个性能丰富的日志记录软件包能当作一个审计工具(audit tool)使用。
Brian W. Kernighan 和 Rob Pike 在他们的"The Practice of Programming" 书中这样写到: "The Practice of Programming"
作为个人的选择,除了得到一大堆程序跟踪信息或一两个变量值以外,我们倾向於不使用排错器。一个原因是在详细而复杂的数据结构和控制流程中很容易迷失;我们发现认真思考并在关键处加入自我检查代码和输出指令,比起一步步看程序要效率高。在日志说明里查找比在明智地放置自我检查代码后的输出里查找要费时。而决定在哪里放置打印指令要比在日志说明里一步步找到关键的代码要省时间。更重要的是,自我检查的排错指令和程序并存;而排错sessions是暂时的。
Logging确实也有它的缺陷。它降低了程序运行的速度。它太冗长,查看时很容易错过。为了减少这些负面影响,log4j 被设计得可靠,高效和灵活。因为,记录日志很少是一个应用程序的主要焦点,log4j API 尽量做到容易被理解和使用。
Loggers, Appenders and Layouts
Log4j 有三个主要组件:loggers , appenders 和layouts 。这三类组件一起应用,可以让开发人员能够根据日志的类型和级别进行记录,并且能在程序运行时控制log信息输出的格式和往什么地方输出信息。
Logger hierarchy
任何logging API 与简单的System.out.println
输出调试信息方法比较,最主要的优点在于它能够关闭一些调试信息输出而不影响其他人的调试。这种能力的实现是假设这些logging空间,也就是所有的可能发生的日志说明空间,可以根据程序开发人员选择的标准进行分类。这一观察以前使得我们选择了category 作为这个软件包的中心概念。但是,在log4j 1.2版本以后,
类取代了Logger
类。对于那些熟悉早先版本的log4j的开发人员来说,Logger类只不过是Category类的一个别名。Category
Loggers是被命名的实体。Logger的名字大小写有区别(case-sensitive),并且它们遵守阶层式的命名规则:
|
例如,叫做"com.foo"的logger是叫做 "com.foo.Bar"的logger的父辈 。同样地,"java"是"java.util" 的父辈,是"java.util.Vector"的前辈。大多数开发人员都熟悉这种命名方法。 "com.foo"
"com.foo.Bar"
"java"
"java.util"
"java.util.Vector"
根(root)logger 位于logger 阶层的最上层。它在两个方面很特别:
- 它总是存在的,
- 不能通过使用它的名字直接得到它。
通过这个类的静态方法Logger.getRootLogger 得到它(指RootLogger)。所有其他的loggers是通过静态方法Logger.getLogger 来实例化并获取的。这个方法Logger.getLogger把所想要的logger的名字作为参数。 Logger类的一些其它基本方法在下面列出:
package org.apache.log4j; public class Logger { // Creation and retrieval methods: public static Logger getRootLogger(); public static Logger getLogger(String name); // printing methods: public void debug(Object message); public void info(Object message); public void warn(Object message); public void error(Object message); public void fatal(Object message); // generic printing method: public void log(Level l, Object message); } |
Loggers可以 被指派优先级别。Level.html#DEBUG">DEBUG , INFO , WARN , ERROR 和FATAL 这组级别在org.apache.log4j.Level
类中有定义。你也可以 通过Level类的子类去定义你自己的优先级别,尽管我们不鼓励你这样做。在后面我们会讲到一个更好的方法。
如果一个logger没有被指定优先级别,它将继承最接近的祖先所被指定的优先级别。下面是更多关于优先级别的信息:
|
要保证所有的loggers最终都继承一个优先级别,root logger总是有一个被指派的优先级。
下面是具有各种指派优先级别值的四个表格,以及根据上面的规则所得出的继承优先级别。
Logger name(名称) | 指派 级别 | 继承 级别 |
---|---|---|
根 | Proot | Proot |
X | none | Proot |
X.Y | none | Proot |
X.Y.Z | none | Proot |
在上面的示例1中,只有root logger被指派了级别。这个级别的值,Proot
,被其它的loggers X, X.Y
和 X.Y.Z
继承了。
Logger name(名称) | 指派 级别 | 继承 级别 |
---|---|---|
根 | Proot | Proot |
X | Px | Px |
X.Y | Pxy | Pxy |
X.Y.Z | Pxyz | Pxyz |
在上面的示例2中,所有的loggers都有一个指派的级别值。不需要级别继承。
Logger name(名称) | 指派 级别 | 继承 级别 |
---|---|---|
根 | Proot | Proot |
X | Px | Px |
X.Y | none | Px |
X.Y.Z | Pxyz | Pxyz |
在示例3中,loggers root
, X 和
分别被指派级别值X.Y
.ZProot
, Px
和Pxyz
。Logger X.Y 从它的父辈X那里继承它的级别值。
Logger name(名称) | 指派 级别 | 继承 级别 |
---|---|---|
根 | Proot | Proot |
X | Px | Px |
X.Y | none | Px |
X.Y.Z | none | Px |
在示例4中,loggers root
和X 分别被指派级别值Proot
和Px
。Logger X.Y
和X.Y.Z
继承它们最接近的父辈X的被指派的级别值。
日志请求是通过调用一个日志实例的打印方法(之一)而产生的。这些打印方法是log
4j/Logger.html#debug(java.lang.Object)">debug, info , warn , error , fatal 和 log。
根据定义,打印方法决定一个日志请求的级别。例如,如果c是一个日志实例,那么语句c.info("..") 就是级别为INFO的一个日志请求。 c.info("..")
只有一个日志请求(A logging request)的级别高于或等于它的logger级别的时候才能够被执行 。否则,则被认为这个日志请求不能被执行 。一个没有被定义优先级别的logger将从层次关系中的前辈那里继承优先级别。这个规则总结如下:
这个规则是log4j的核心。它假设级别是有先后顺序的。对于标准的优先级别来说,DEBUG < INFO < WARN < ERROR < FATAL
。
|
这里是一个关于这个规则的例子:
// get a logger instance named "com.foo" Logger logger = Logger.getLogger("com.foo" ); // Now set its level. Normally you do not need to set the // level of a logger programmatically. This is usually done // in configuration files. logger .setLevel(Level.INFO ); Logger barlogger = Logger.getLogger("com.foo.Bar" ); // This request is enabled, because WARN >= INFO . logger.warn ("Low fuel level."); // This request is disabled, because DEBUG < INFO . logger.debug ("Starting search for nearest gas station."); // The logger instance barlogger, named "com.foo.Bar", // will inherit its level from the logger named // "com.foo" Thus, the following request is enabled // because INFO >= INFO . barlogger.info ("Located nearest gas station."); // This request is disabled, because DEBUG < INFO . barlogger.debug ("Exiting gas station search"); |
以一样的叁数名字调用getLogger
方法,返回的reference总是指向完全相同的logger对象。
例如,在这里:
Logger x = Logger.getLogger("wombat"); Logger y = Logger.getLogger("wombat"); |
x和y指向完全 相同的logger对象。
因此,通过这种方式可以配置一个logger,而不需要传递references就能在其他地方得到相同的实例。在生物的父子关系中父母总是排放在孩子们前面, log4j loggers与此有相互矛盾的地方,那就是log4j loggers可以以任何顺序被产生和配置。特别的是,一个"parent" logger 会找到并连接他的后代,即使他是在他们之后被定义。
Log4j环境通常是在程序被初始化的时候被配置的。最好的方式是通过阅读一个配置文件去配置。我们会马上讨论到这方面的内容。
Log4j使得通过软件组件的名称去定义loggers的名字很容易。这可以通过在每个类中静态地instantiating一个logger,让logger的名字与这个合格的java类文件名相同来完成。这是一种有用并且直观的定义loggers的方式。因为日志的输出带有产生它们的logger的名字,这种命名策略使我们能够很方便地识别这些log信息的来源。不过,尽管这是通用的一种loggers命名策略,Log4j没有限制怎样对loggers进行命名。开发程序员可以根据自己的喜好随意定义 loggers。 software component
当然,至今所知的最好的命名策略还是以它们所在的类的名称来命名 loggers。
Appenders and Layouts
基于自身的logger选择性地使用或不使用日志请求(logging requests )的能力仅仅整个Log4j能力的一部分。Log4j允许将log信息输出到许多不同的输出设备中。用log4j的语言来说,一个log信息输出目的地就叫做一个appender 。目前,log4j 的appenders可以将log信息输出到 console ,files ,GUI components,remote socket servers, JMS ,NT Event Loggers,和 remote UNIX Syslog daemons。它还可以同时 将log信息输出到多个输出设备中。 NT Event Loggers
多个appenders可以和一个logger连接在一起。
使用addAppender 方法把一个appender加入到给定的logger上。一个给定的 logger的每一个被允许的日志请求都会被传递给这个logger的所有appenders,以及阶层中高级别的appenders。换句话说appenders是从logger阶层中不断添加地被继承的。例如,一个 console appender加给了root logger,那么,这个root logger所有被允许输出的日志信息将被输出到console。如果你又给一个名字为C的logger添加了一个 file appender,那么C 以及C的子辈的所有被允许的日志信息将被同时输出到 file appender和 console appender。可以通过把additivity flag设置为false
来覆盖这个默认的行为从而使appender的继承关系不再是添加性的。 Each enabled logging request for a given logger will be forwarded to all the appenders in that logger as well as the appenders higher in the hierarchy. setting the additivity flag
支配appender添加性的规则总结如下:
|
Logger name(名称) | 添加的 Appenders | Additivity 旗标 | 输出目标 | 注释 |
---|---|---|---|---|
根 | A1 | not applicable | A1 | Root logger是无名的,但是可以通过Logger.getRootLogger() 来访问。Root logger没有附带默认的appender。 |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | "x" 和root logger里的Appenders。 |
x.y | none | true | A1, A-x1, A-x2 | "x" 和root logger里的Appenders。 |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | "x.y.z", "x" 和root logger里的Appenders。 |
安全 | A-sec | false | A-sec | 因为additivity flag被设置为 false ,所以没有appender继承积累。 |
security.access | none | true | A-sec | 因为"security" logger里的additivity flag被设置为false ,所以仅仅只有"security" logger的appenders。 |
通常,用户不仅希望自己指定log信息的输出目的地,而且,他们还希望指定 log信息的输出格式。这可以通过和appender相关的layout 实现。Layout负责根据用户的需要去格式化log信息的输出,而appender负责将一个格式化过的 log信息输出到它的目的地。 PatternLayout 是标准log4j发行包中的一部分,它让用户根据和C语言中的printf
方法相似的转换模式指定输出格式。
例如,具有"%r [%t] %-5p %c - %m%n" 转换格式的PatternLayout 将输出以下的式样:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一个区域是从程序开始运行到输出日志信息所用的毫秒数。第二个区域是产生日志请求的线程。第三个区域是这个log语句的优先级别。第四个区域是和日志请求相关联的logger名字。在'-' 之后的文字是这个log信息的内容。
同样重要的是,log4j 将根据用户指定的标准来表达log信息的内容。例如,如果你经常需要日志记录Oranges
,Oranges是你当前项目中使用的一个对象类型,那么你可以注册一个OrangeRenderer
,这样每当需要日志记录一个 orange时,OrangeRenderer就会被调用。
对象的表达遵照类阶层(class hierarchy)形式。例如,假设oranges是 fruits,你注册了一个
,那么,包括oranges在内的所有的fruits 都将由FruitRenderer来表达,除非你自己为orange注册了一个特定的 FruitRenderer
OrangeRenderer
。
Object renderers必须实施ObjectRenderer 界面。
配 置
在程序代码中插入这些日志请求需要相当大的工作量。调查显示,大约%4左右的代码是logging。因此,即便是中等大小的应用程序也需要在它们的代码中至少包含有几千行的log语句。就从这个数目来看,管理这些log语句而不用人工地去修改它们是十分重要的。
Log4j环境是完全能够通过编程来配置的。但是使用配置文件去配置则更灵活。目前,Log4j的配置文件是以XML格式和JAVA properties (key=value) 格式编写的。
假设我们有个叫MyApp
的程序使用log4j,让我们来看看这是怎样做到的:
import com.foo.Bar; // Import log4j classes. import org.apache.log4j.Logger; import org.apache.log4j.BasicConfigurator; public class MyApp { // Define a static logger variable so that it references the // Logger instance named "MyApp". static Logger logger = Logger.getLogger(MyApp.class); public static void main(String[] args) { // Set up a simple configuration that logs on the console. BasicConfigurator.configure(); logger.info("Entering application."); Bar bar = new Bar(); bar.doIt(); logger.info("Exiting application."); } } |
类首先引入log4j的相关类,然后定义一个命名为MyApp的静态logger变量,而这个名字恰好和MyApp的类名一样。MyApp
MyApp
类还使用了被定义在com.foo
包中的Bar
类:
package com.foo; import org.apache.log4j.Logger; public class Bar { static Logger logger = Logger.getLogger(Bar.class); public void doIt() { logger.debug("Did it again!"); } } |
通过调用BasicConfigurator.configure 方法产生一个相当简单的log4j的设置。这个方法将一个 ConsoleAppender 添加到root logger,从而让log信息输出到 console。通过把PatternLayout 设置为 %-4r [%t] %-5p %c %x - %m%n来确定输出格式。
注意,默认的root logger被指派为Level.DEBUG
。
MyApp的输出是这样的:
0 [main] INFO MyApp - Entering application.36 [main] DEBUG com.foo.Bar - Did it again!51 [main] INFO MyApp - Exiting application.
下面的图形描绘了在调用BasicConfigurator.configure
方法之后,MyApp
的对象图表。
注意,log4j 的子代loggers只和它们现有的前辈链接。在这里,名字叫
的logger直接和com
.foo.Barroot
logger链接,因此绕过了没有被使用的com 或com.foo
loggers。这样极大地提高了log4j的性能并减少了内存(memory)的使用。
通过调用BasicConfigurator.configure
方法来配置MyApp
类。其它的类只需要引入org.apache.log4j.Logger
类,获取它们想要使用的loggers,就可以输出 log。
先前的例子总是输出同样的log信息。幸运的是,很容易修改MyApp
程序就可以在程序运行时对log输出进行控制。下面是略加修改后的版本:
import com.foo.Bar; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; public class MyApp { static Logger logger = Logger.getLogger(MyApp.class.getName()); public static void main(String[] args) { // BasicConfigurator replaced with PropertyConfigurator. PropertyConfigurator.configure(args[0]); logger.info("Entering application."); Bar bar = new Bar(); bar.doIt(); logger.info("Exiting application."); } } |
这个例子中MyApp
指示PropertyConfigurator
方法去解读配置文件并设置相应的logging 。
这里是一个配置文件的示例,这个配置文件产生和前面BasicConfigurator
例子完全一样的输出结果:
# 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.PatternLayoutlog4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n |
假设我们不再需要com.foo
软件包里任何组件的日志输出,下面的配置文件展示了达到这一目的的一种可能的方法:
log4j.rootLogger=DEBUG, A1log4j.appender.A1=org.apache.log4j.ConsoleAppenderlog4j.appender.A1.layout=org.apache.log4j.PatternLayout# Print the date in ISO 8601 format log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n# Print only messages of level WARN or above in the package com.foo.log4j.logger.com.foo=WARN |
由这个文件所配置的MyApp
的日志输出如下:
2000-09-07 14:07:41,508 [main] INFO MyApp - Entering application.2000-09-07 14:07:41,529 [main] INFO MyApp - Exiting application.
因为logger
没有指定的优先级别,它就从com.foo中继承优先级别,而com.foo的优先级别在配置文件中被设置为WARN。 com.foo
.BarBar.doIt
方法里的 log语句的级别为DEBUG,比WARN级别低。所以,doIt()
方法的日志请求就被压制住了。
这里是另一个使用多个appenders的配置文件。
log4j.rootLogger=debug, stdout, R log4j.appender.stdout =org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayout# Pattern to output the caller's file name and line number.log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%nlog4j.appender.R =org.apache.log4j.RollingFileAppenderlog4j.appender.R.File=example.loglog4j.appender.R.MaxFileSize=100KB # Keep one backup filelog4j.appender.R.MaxBackupIndex=1log4j.appender.R.layout=org.apache.log4j.PatternLayoutlog4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n |
调用以这个配置文件增强了的MyApp会把下列输出信息输出到控制台(console)上。
INFO [main] (MyApp2.java:12) - Entering application.DEBUG [main] (Bar.java:8) - Doing it again! INFO [main] (MyApp2.java:15) - Exiting application.
另外,当root logger增加了第二个appender时,log信息将同时也被输出到
文件中。当example.log文件达到100KB 后,example.log文件将被rolled over。当roll-over 发生时,example.log 的老版本将自动被移到 example.log
example.log.1
中去。
注意,要获得这些不同的logging行为并不需要重新编译代码。我们还可以简单地通过修改log配置文件把log信息输出到UNIX Syslog daemon中,把所有 com.foo
的日志输出转指向NT Event logger 中,或者把log事件输出到远程 log4j服务器中,当然它要根据局部服务器规则进行log,例如可以把log事件输出到第二个log4j服务器中去。
默认的初始化过程
Log4j库没有对它的环境作任何假设。特别是,没有默认的log4j appenders。不过在一些精细定义过的情况下,这个Logger
类的静态的initializer会试图自动配置log4j。 Java语言确保一个类的静态的initializer在这个类被装载到内存里时被调用一次,而且仅仅一次。这点很重要,要记住不同的classloaders会装载同一个类的不同复制版。这些同一个类的不同复制版在JVM看来是完全不相关的。
默认的初始化在这样的环境中很有用处,那就是同一个程序依据运行时的环境作不同用处。例如,同样一个程序可以在web-server的控制下作为单独的程序,作为一个applet,或者作为一个servlet被使用。
默认的初始化运算法则定义如下:
- 把log4j.defaultInitOverride 的系统属性设置为 "false"以外的任何值将会造成 log4j跳过默认的初始化过程。
- 把
这个string变量设置为log4j.configuration 系统属性的值。最好的方法指定默认初始化文件是通过log4j.configuration系统属性来指定。在log4j.configuration系统属性没有被定义的情况下,把resource这个string变量设置成它的默认值"log4j.properties"。resource
- 把
resource
变量转换为一个URL。 - 如果这个
resource
变量不能转换为一个URL,例如,因为MalformedURLException
的缘故,那么就通过调用 org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)在 classpath上搜寻resource,它会返回一个URL。注意, string "log4j.properties"是一个不合式的URL。org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)
有关搜寻地址列单,请参看Loader.getResource(java.lang.String) 。
- 如果不能找到URL,那就放弃默认的初始化。否则,从URL配置log4j 。
Configurator .html">PropertyConfigurator将被用于解读URL来配置log4j,除非这个URL以".xml"扩展符结束,若这个URL以".xml"扩展符结束,DOMConfigurator 则被使用。你可以选择性地指定一个客户自己的configurator。log4j.configuratorClass 系统属性的值就是你客户自己的configurator的类名。你指定的客户configurator必须 实施Configurator接口。