初始化servlet
还可以使用一个特别的servlet来进行log4j初始化。这里就是个示例:
package com.foo; import org.apache.log4j.PropertyConfigurator; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.io.IOException; public class Log4jInit extends HttpServlet { public void init() { String prefix = getServletContext().getRealPath("/"); String file = getInitParameter("log4j-init-file"); // if the log4j-init-file is not set, then no point in trying if(file != null) { PropertyConfigurator.configure(prefix+file); } } public void doGet(HttpServletRequest req, HttpServletResponse res) { } } |
在web.xml文件里为你的网络应用程序定义下面的servlet。
<servlet> <servlet-name>log4j-init</servlet-name> <servlet-class>com.foo.Log4jInit</servlet-class> <init-param> <param-name>log4j-init-file</param-name> <param-value>WEB-INF/classes/log4j.lcf</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> |
编写一个initialization servlet 是最灵活的方式来初始化log4j。不受任何限制,你可以在这个servlet的init()
方法里放入任何代码。
Nested Diagnostic Contexts
实际情况下的大多数系统都需要同时处理多个客户端问题。在这种系统的典型的多线程实施中,通常是不同的线程去分别处理不同的客户需求。Logging特别适合于复杂的程序跟踪和排错。一个通常的处理办法是通过给每个客户产生一个新的分离开的logger来达到把不同的客户的日志输出信息区分开来。但这促进了loggers的增殖,加大了logging的管理负担。
一个更简洁的技术是独特地标记来自于同一个客户的每一个日志请求。Neil Harrison 在他的书中"Patterns for Logging Diagnostic Messages," in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 对这个方法进行了描述。 Pattern Languages of Program Design 3
要独特地标记每个日志请求,用户把上下文信息送入NDC,NDC是 Nested Diagnostic Context 的缩写。NDC类展示如下。
public class NDC { // Used when printing the diagnostic public static String get(); // Remove the top of the context from the NDC. public static String pop(); // Add diagnostic context for the current thread. public static void push(String message); // Remove the diagnostic context for this thread. public static void remove(); }
NDC类是作为一个保存线程上下文的stack 来独个线程(per thread) 管理的。注意,org.apache.log4j.NDC
类中所有的方法都是静态的。假设NDC打印功能被打开,每一次若有日志请求,相应的log4j组件就把这个当前线程的整个 NDC stack包括在日志输出中打印出来。这样做不需要用户干预,用户只需要在代码中明确指定的几点通过push
和pop
方法将正确的信息放到NDC中就行了。相反,per-client logger方法需要在代码中作很多更改。
为了说明这一点,我们举个有关一个servlet把信息内容发送到多个客户的例子。这个Servlet程序在开始接到客户端的请求,执行其它代码之前,首先创建一个NDC。该上下文信息可能是客户端的主机名,以及其他请求中固有的信息,通常是包含在cookies中的信息。因此即便这个Servlet程序可能同时要服务于多个客户,由相同的代码启动的这些logs,比如属于同一个logger,它们仍然能够被区分开来,因为不同的客户端请求具有不同的NDC stack。这与在客户请求期间把一个实例化的logger传递给所有要被执行的代码的复杂性形成了反差。
然而,一些复杂的应用程序,比如虚拟网络服务器,必须依据虚拟主机的上下文语言环境,以及发布请求的软体组件来作不同的log。最近的log4j发行版支持多阶层树。这一功能的加强允许每个虚拟主机拥有它自己的logger阶层版本。
性能
一个经常提出的争议就是logging的运算开销。这种关注是有道理的,因为即便是一个中等大小的应用程序至少也会产生几千个log输出。许多工作都花费在测量和改进logging性能上。Log4j声明它是快速和灵活的:速度第一,灵活性第二。
用户需要清楚地了解下面这些与性能相关的问题:
- Logging performance when logging is turned off.
当logging被完全关闭或只是set of levels被关闭,日志请求的开销是方法的调用和整数的比较。在一个233 MHz Pentium II机器上,这种开销通常在5 to 50 毫微秒范围内。 set of levels
不过,方法的调用包含有参数的建造上的“隐闭”开销。
例如下面的logger
cat
程序段中:logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
不管message被日志记录与否,构造message参数的开销还是有的,比如说,把整数i 和数组entry[i]
转化为String,连接中间字串。参数构造的这种开销可能很高,它依赖于所介入的参数数量有多少。为了避免这种参数构造开销,把以上的代码段改写为:
if(logger.isDebugEnabled() { logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); }
如果排错功能不被使用,就不会有参数构造上的开销。但是,另一方面,如果 logger的排错功能被起用,就会有俩倍的开销用于评估logger是否被起用:一次是判断
,一次是判断debug是否被启用。但这不是极重的负担,因为评估logger的时间只有整个log语句执行时间的1%debug
Enabled在log4j中,把日志请求作为Logger类的实例。Logger是类而不是接口,这主要是为了减少程序调用的开销,但牺牲了接口所能带来的灵活性。
有些用户使用预处理或compile-time技术来编译所有log语句。这样logging方面的性能是很好。但是,因为resulting application binary没有包含任何log语句,你不能对这个二进制程序起用logging。在我看来,这是为了小的性能增加而付出大的代价。
- The performance of deciding whether to log or not to log when logging is turned on.
本质上影响性能的因素是logger的层次关系。当logging功能被打开时,log4j仍然需要把log请求的级别去与request logger的级别作比较。不过,有些loggers 并没有指派的优先级别,但它可以从它的上一层logger那里继承优先级别。因此在继承优先级之前,logger可能需要搜索它的ancestors。
Log4j在这方面做了很大的努力,以便使这种阶层的优先级别搜寻(hierarchy walk )尽可能的快速。例如,子代loggers仅仅只和它们现有的ancestors链接。在前面的
BasicConfigurator
示例中,叫做
的logger 直接与 root logger链接,绕过了不存在的com或com
.foo.Barcom.foo
loggers。这极大地提高了优先级别搜寻的速度。阶层的优先级搜寻(walking the hierarchy )的开销在于它比logging完全关闭时要慢三倍。
- Actually outputting log messages
这里讲的是log输出的格式化和把log信息发送到目标所在地的开销。Log4j在这方面也下了大力气让格式化能尽快执行。对appenders也是一样。通常情况下,格式化语句的开销可能是100到300微秒的处理时间。确切数字请参看 org.apache.log4.performance.Logging 。
尽管log4j具有许多功能特性,但速度是第一设计目标。为了提高性能,一些 log4j的部件曾经被重写过许多次。即使这样,log4j的贡献者们不断提出新的优化办法。你应该很惊喜地发现当以SimpleLayout 来配置时,性能测试显示使用 log4j日志和使用System.out.println
日志同样快。
结论
Log4j是用Java编写的一个非常流行的logging开发包。它的一个显著特性之一是在loggers里运用了继承的概念。使用这种logger的层次关系,就可能准确地控制每一个log语句的输出。这样减少了log信息的输出量并降低了logging的开销。
Log4j API的优点之一是它的可管理性。一旦log语句被插入到代码中,他们就能被配置文件控制而无需重新编译源代码。Log信息的输出能够有选择地被起用或关闭,用户能够按照自己选择的格式将这些log信息输出到许多不同的输出设备中。Log4j软件包的设计是在代码中保留log语句的同时不造成很大的性能损失。