Ceki Gülcü and Chris Taylor
译者声明:
1. 这是根据最新的log4j(jakarta-log4j-1.2.8)的开发包自带文档的FAQ翻译的
2. 译者尽力保持原文格式,以便于读者查找原文
3. 粉红色的标题代表原文只有标题,而没有相应的内容
4. 对一些关键词都带有原文单词,便于阅读
5. 原文中比较生僻的单词,译者都是参考金山词霸,对找不到的组合词都是保持原文。
6. 对比较拗口的说法,译者尽量采用意译的方法,同时保持原文。
2004.6.16
· log4j有javadoc文档吗?(Is there javadoc documentation for log4j? )
· 有其他的日志包吗?(What other logging packages are there? )
· 什么是记录器?
· 除了把字符串数组作为参数之一输出,调试方法还有其他作用吗?(What is the use of the debug method expecting a String array as one of its parameters?)
· 为什么要介绍记录器(Logger)类,并且我怎样才能从基于以前实现(implementation)的字符串移植?(Why was the Logger class introduced and how do I migrate from the previous String based implementation?)
· 我怎样才能用静态模式(in a static block)的方式得到一个高质量(fully-qualified)的类名
· 记录器实例好像仅仅可以被创建,为什么没有一个方法可以移除记录器实例?
· 按照不同的等级,可以把日志输出定向到不动的输出源(appender)吗?
· 假如我有许多跨越多个主机(可能跨越多个时区)的处理进程用上述方法记录日志到相同的文件,时间戳会发生什么事情?
· 为什么在J2EE或者WAR应用程序中log4j不能发现我的属性文件?
· 当我使用NTEventLogAppender类时,Windows NT事件观察器(Windows NT Event Viewer)对丢失我的事件信息描述行为会有什么样的反应?
· 当我使用NTEventLogAppender类时,为什么我不能映射我的记录器名到显示在NT事件记录器(NT Event Log)中的记录器
· 我为什么要把我的log4j扩展捐赠(donate)给项目呢?
Log4j是一款帮助程序员输出日志信息到输出目的地的工具
在一个带有问题的应用程序中,使用日志来定位错误使很有帮助的。在运行情况下,不修改二进制应用程序使用log4j是可以使日志起作用的。Log4j包被设计用来使日志信息可以用漂码(shipped code)的形式保存,并且不会导致高性能开销。它遵循记录速度(即使没有记录行为)第一的原则
同时,日志输出可能是那么的庞大以至于无法记录。Log4j中独一无二的特色之一是分级记录器(Logger)的概念。使用记录器,使有选择的按任意粒度控制输出日志成为可能。
Log4j是按两个特殊目来设计的:速度和灵活性。在这两个必备要求中间,很难找到一个平衡点。不过,我相信log4j达到最好的平衡点
Log4j是不可靠的,它是一个best-effort和fail-stop的日志系统
提到fail-stop,我们意思是log4j运行在有导致系统崩溃的潜在错误的运行状态,它不会抛出意料之外的异常。假如由于某些原因,导致log4j抛出一个未被捕捉的异常,请发email到[email protected]邮件列表
还有,当log4j指定的输出流(output stream)处于未打开、不可写或者已满的状态时,它不会转向输出到System.out和System.err。这就避免了由于日志记录失败,使用户的终端充满错误信息,因而导致另一个正在运行的程序一团槽。然而,log4j将输出一个单条信息到System.err表明日志不能正常运行。
· Log4j是和JDK 1.1.x 兼容的。
· DOMConfigurator是基于the DOM Level 1 API。DOMConfigurator.configure(Element)方法将和任何可以把XML文件解析成DOM书(DOM tree)的XML解析器很好工作,DOMConfigurator.configure(String filename)方法和它的变量需要一个和JAXP兼容的XML解析器,例如Xerces(译注:这也是Apache下面的一个开源项目)或者SUN公司的解析器。编译DOMConfigurator时要求JAXP解析器要在classpath环境变量的路径下面
· org.apache.log4j.net.SMTPAppender类依赖于JavaMail API。它已经和JavaMail API的1.2版本测试过了。JavaMail API需要JavaBeans Activation Framework包
· org.apache.log4j.net.JMSAppender类需要JMS API和JNDI的存在
· log4j的测试代码(test code)依赖于JUnit测试框架
参见examples/路径。
· Log4j在速度方面已经被优化过了
java.io.OutputStream
) ,输出器(java.io. Writer
) ,一个套接字远程服务器(a remote server using TCP),一个远程的UNIX Syslog守护线程(Unix Syslog daemon),一个远程的JMS的监听线程,NT事件的记录器,甚至发email。 对,log4j是线程安全的
可以用多种方式定制日志输出。而且,可以通过实现一个自己的布局器(Layout)来完全覆盖输出格式
这是一个使用PatternLayout布局器的输出实例,这个布局器带"%r [%t] %-5p %c{2} %x - %m%n"的转换格式
176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
225 [main] INFO examples.SortAlgo - Entered the sort method.
262 [main] DEBUG SortAlgo.OUTER i=1 - Outer loop.
276 [main] DEBUG SortAlgo.SWAP i=1 j=0 - Swapping intArray[0] = 1 and intArray[1] = 0
290 [main] DEBUG SortAlgo.OUTER i=0 - Outer loop.
304 [main] INFO SortAlgo.DUMP - Dump of interger array:
317 [main] INFO SortAlgo.DUMP - Element [0] = 0
331 [main] INFO SortAlgo.DUMP - Element [1] = 1
343 [main] INFO examples.Sort - The next log statement should be an error message.
346 [main] ERROR SortAlgo.DUMP - Tried to dump an uninitialized array.
at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
at org.log4j.examples.Sort.main(Sort.java:64)
467 [main] INFO examples.Sort - Exiting main method.
第一个字段是从程序开始运行到该行日志输出的毫秒数。第二个字段是输出日志的线程。第三个是日志描述的级别。第四个字段是发出日志请求的记录器(logger making the log request)的最右面两个部分的组成。第五个字段(恰好在‘-’的前面)是嵌套诊断环境(nested diagnostic context (NDC)).注意嵌套诊断环境(NDC)可以是空的,就像刚开始的两个描述。紧跟在‘-’后面的是描述信息
记录器处于log4j的核心地位。记录器定义了一个层次(hierarchy),并且给程序员运行时的控制是否打印控制的描述信息
记录器被分配级别。一个日志描述的打印依赖于他的级别和记录器
阅读log4j的使用手册(log4j manual)获取更多信息
日志行为可以被设置在一个配置文件中,在运行时可以解析这个文件。使用配置文件,程序员可以定义一个记录器并且设置它的级别
PropertyConfigurator类定义了一个特殊的配置文件的格式。也可以参考examples/Sort.java实例的配置文件。
配置文件可以是XML文件。参考log4j.dtd和org.log4j.xml.DOMConfigurator类获取更多信息
参考各种为实现特别配置选项的布局(Layout)和输出源(Appender)组件。
包括配置文件,使用者可以使附属于一套级别的所有信息无效,参考下一条。
例如记录器l,内容如下,
l.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
导致构造信息参数开销的是:转化整型i值和数组entry[i]为字符串;连接字符串的媒介。不管是否记录信息都会发生这些事情。
假如你担心速度,可以这样些,
if(l.isDebugEnabled()) {
l.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
使用这种方法,假如你在调试时不输出日志,就不会导致参数构造的开销。另一方面,若记录器是需要调试的,无论记录器是否可调试,都将导致检测的开销(cost of evaluating),这包括两个地方:一个是debugEnabled,一个是debug(译注:就是l.isDebugEnabled()和l.debug(“”)的这两个方法的开销)。这是无关紧要的日常开销,因为检测一个记录器的花费还不到它记录一个日志信息的1%时间。
是,有的
你可以通过位置(locality)来命名记录器(loggers).这证明了用单个类的类名去实例化一个记录器等同于用完整的记录器名,这是一个直截了当地定义记录器的方法。
这种方法有如下好处:
然而,这不是日志命名的唯一方法。一个普通的替代方法是通过功能区域来命名记录器。例如,“数据库”(database)记录器,“远程方法调用”(RMI) 记录器,“安全”(security) 记录器,“XML”记录器。
你可以在功能和本地子范畴(by functionality and subcategorize by locality)两者中选择一个命名记录器,就像"DATABASE.com.foo.some.package.someClass"或者"DATABASE.com.foo.some.other.package.someOtherClass"。
在选择你自己的记录器的名称时,你是完全自由的。Log4j包仅仅允许你按照一个层次来管理你的名称。无论如何,定义这个层次是你的责任
注意,本地化地命名记录器往往通过功能来命名(Note by naming loggers by locality one tends to name things by functionality),因为在大多数的情况下,本地化和功能联系更紧密。
你可能很容易按静态模式(in a static block,译注:我没有找到最佳译法,以后再修改吧J)的方式用X.class.getName()方法获得类X的高质量的名称,。注意,X是类名而不是实例。X.class不会创建一个新的类X的实例
这是一个建议的用例模板:
package a.b.c;
public class Foo {
static Logger logger = Logger.getLogger(Foo.class);
... other code
}
是的。从0.7.0版本,你就可以扩展Layout类来创建你自己的日志格式。输出源(Appenders)也通过你自己选择的布局器(layout)来达到参数化
许多开发者都面临这样问题,面对不同的客户端请求,在相同的类(class)中辨别除日志输出的类别。他们给log4j的爱好者提出了一个独具匠心的机制输出日志到不同的文件。在大多数情况下,这不是一个好的方法。
使用嵌套诊断环境(NDC)来实现很简单的。特别是,当处理一个客户端请求时, 使用NDC.push()将获得客户端的特殊信息,例如主机名、ID或者任何其它区分信息。从这以后,日志输出将自动包括嵌套诊断环境,即使日志被输出到同一个文件你也能区分出不同客户端的请求。
参考NDC和PatternLayout类获得更多信息。NumberCruncher例子展示了怎样用NDC从多个客户端区分出日志输出信息,即使他们共享同一个日志文件
对应用程序,例如虚拟主机web服务器(virtual hosting web-servers),NDC的解决方案是行不通的。到log4j的0.9.0版本,log4j开始支持多层次树(multiple hierarchy trees).因此,依赖当前环境,在相同的记录器中记录不同目标是可能的。
Log4j使用JavaBean的方式来配置。
因此,在FooBarAppender类中任何一个赋值方法(setter method)对应一个配置选项。例如,RollingFileAppender类的setMaxBackupIndex(int maxBackups)方法对应maxBackupIndex选项。选项的第一个字母可以是大写,也就是说(i.e.) MaxBackupIndex和maxBackupIndex是相同的,但是MAXBACKUPIndex和mAXBackupIndex是不同的。
布局器选项也可以通过赋值方法(setter methods)来定义。其他的log4j组件大多数也是这样做的。
定义一个“removed”记录器不是一件简单的事情,该记录器还要可以被使用者引用。未来的版本可能会包含一个删除方法(remove method)在Logger类中
是的,可以这样。从AppenderSkeleton类扩展一个输出源(大多数的log4j输出源都是扩展AppenderSkeleton类),设置该输出源的入口选项(Threshold option)来过滤所有的日志事件,这些日志事件的入口选项(Threshold option)的值比已经设置的级别低。
例如,设置一个输出源的入口选项值为DEBUG,这就允许INFO, WARN, ERROR 和FATAL级别的信息可以连同DEBUG级别的信息一起记录。这是可接受的,因为没有周围的INFO, WARN, ERROR 和 FATAL的信息,DEBUG信息就没有什么作用
这种规则通常是使用者想要的最好封装,因为这是和他(她)心中已有的解决案是相反的(as opposed to her mind-projected solution,译注:这句话感觉很拗口,原文意思好像有误)。
参考examples/sort4.lcf查找一个入口(threshold)配置的实例
假如你必须使用精确级别匹配过滤事件,那么你可以让任何输出源都继承LevelMatchFilter类来过滤日志事件。
你可以让每个SocketAppender类都有一个进程日志。接受方的套接字服务器(SocketServer)(或者简单套接字服务器(SimpleSocketServer))可以接受所有的事件并把它们发给一个独立的日志文件。
当日志事件被创建的时候,这个时间戳也被创建,也就是在debug, info, warn, error or fatal方法被调用的时候。这是不受他们(译注:此处代表日志记录)到达远程服务器的时间影响的。由于时间戳是以UTC的格式保存在事件中的,所以他们可以显示在相同的时区,这个时区是创建这个日志文件的服务器所处的时区。由于不同机器的时钟是不可以同步的,所以在不同服务器上产生的事件之间,这会导致时间间隔(time interval)的冲突。
虽然这是一个有目的的行为,但它最近变的太依赖于版本1.0.4和版本1.1b1之间的bug发现。1.0.4以前的版本可以在转换器中产生他们自己的时间戳。在这种情况下,日志文件里的时间戳会按顺序全部显示,这个时间戳是当他们到达日志服务器主机时,服务器依赖于本地时钟产生的。
简单的回答是:log4j的类和属性文件没有被包含在类装载器(classloader)的范围内。
详细的答案是(并且讲解这是怎么回事):J2EE或者Servlet容器利用java的类装载系统(class loading system)。Sun公司在Java2的版本中改变类装载的策略,在Java2中类装载器是按照层次的父子关系(hierarchial parent-child relationship)来设计。但一个子类装载器(译注:子类装载器的意思是子-类装载器,而不是子类-装载器,这是两个不同的概念,注意读法,这是汉语的语义误解导致的)需要发现一个类或者资源是,它第一步会把请求委托给父类装载器(译注:见子类装载器的译注)
Log4j仅仅使用默认的Class.forName()机制来装载类,资源也被按照这样处理。参考java.lang.ClassLoader的文档获得更详细信息
因此,假如你有问题,试着自己装载类和资源。假如你没有发现,log4j也不会发现。:)
Yes. Both the DOMConfigurator and the PropertyConfigurator support automatic reloading through the configureAndWatch
APIs. See the API documentation for more details.
是的。DOMConfigurator类和PropertyConfigurator类通过configureAndWatch接口支持自动加载。参考API文档获得更详细信息
NT事件观察器依赖于消息资源动态链接库(message resource DLLs)来正确显示事件消息. NTEventLogAppender.dll包含了这些消息资源,但是DLL必须要拷贝到%SYSTEMROOT%\SYSTEM32目录才可以正确工作
不幸的是,记录器名是被硬编码在消息资源DLL中的(参考前面关于NTEventLogAppender类的问题),因此没有任何简易的办法覆盖那些动态的东西… 事实上,我认为这是不可能的,因为你不得不为每个应用程序修改DLL资源。无论如何,以前大多数的本地应用程序都没有使用记录器的专有特性…
相对于GNU的公共许可证(GPL),Apache软件许可证并没有对你的扩展设置更多的要求。通过扩展,我们得到了完全新的代码,用这些代码调用log4j的类。根据你的需要,你有权力来自由地扩展log4j。注意,你不可以把你扩展过的代码分发到太广的人群
我们非常注重不修改log4j的客户端代码,因此log4j的新版本是向后兼容以前版本的。我们是不太关心log4j内部的API(We are a lot less scrupulous with the internal log4j API, 译注:我没有理解这句话的意思,我认为这句话有误,他的原意可能是不注重log4j内部相互之间的接口调用,但对开放的外部接口不是如此)。因此,假如你设计你的扩展与版本n恰好吻合,然后log4j的版本n+1出来了,你将很可能需要修改扩展的部分来与新版本相适应。因此,你将被迫花费宝贵的时间来与新版本兼容。这就是经常提到的“愚蠢税收”(stupid-tax)。通过捐赠代码,并让其成为标准的一部分,你将节省不必要的维护工作。
假如你的扩展是有用的,最终会有人写一个扩展来提供相同或者相似的功能。你的开发工作将是浪费的。除非你的log4j扩展是关键业务,否则没有任何理由不捐赠你的代码项目(译注:此处的项目是指apache的log4j项目)
1. 给你捐献的代码写一个测试用例
没有什么比在调试(也就是日志)代码时发现bug更刺激了。写测试用例要花费一定的时间,但是对于一个被广泛使用的库是至关重要的,例如log4j。写一个让你受追随者尊敬的测试用例需要花费很大的努力和很长的时间。
2. 坚持现有的缩进风格,即使你讨厌这种方法。
改变现有的缩进风格会让代码很难理解。让你自己辛苦,但是别人会因此而很轻松。Log4j遵循java语言的代码风格(Code Conventions for the JavaTM Programming Language)
3. 努力让代码支持JDK1.1的API
Log4j的重要的优点是它和JDK 1.1.x完全兼容的。
4. 保持代码简洁,小巧和快速。
这是和应用程序有关的,而和日志无关。
5. 在相关的文件中的开头,辨别你自己是否是捐助者。
6. 对你代码要付责任
创作软件和马拉松赛跑非常相似,它需要时间和耐力
7. 我提及要坚持缩进风格了吗?
8. 我提及要写测试用例了吗?
Log4j的项目被放置在http://jakarta.apache.org/log4j/