在现实世界中的系统经常不得不同时处理多个客户端请求。在这样的一个典型的多线程的系统中,不同的线程将处理不同的客户端。一个常见的区分每个客户端所输出的Logging的方法是为每个客户端实例化一个新的独立的Logger。这导致Logger的大量产生,管理的成本也超过了logging本身。
嵌套诊断环境NDC
在多用户并发的环境下,通常是由不同的线程分别处理不同的客户端请求。此时要在日志信息中区分出不同的客户端,区分两个客户的日志输出的常用方法是单独为每个客户端实例化新类别。但是,该方法会增加类别数量,并增加日志记录的管理开销。
Log4J巧妙地使用了Neil Harrison提出的“NDC(嵌套诊断环境)”机制来解决这个问题。Neil Harrison(请参阅参考资料)想出了一个简便方法,可以唯一标记从同一个客户机交互启动的每一个日志记录请求。Log4J为同一类别的线程生成一个Logger,多个线程共享使用,而它仅在日志信息中添加能够区分不同线程的信息,该信息可能是发出请求的主机名、IP地址或其它任何可以用于标识该请求的信息。这样,由于不同的客户端处理线程具有不同的NDC堆栈,即使这个Servlet同时生成多个线程处理不同的请求,这些日志信息仍然可以区分出来,就好像Log4J为每一个线程都单独生成了一个Logger实例一样。
在Log4J中是通过org.apache.log4j.NDC实现这种机制的。使用NDC的方法也很简单,步骤如下:
1. 在进入一个环境时调用NDC.push(String),然后创建一个NDC;
2. 所做的日志操作输出中包括了NDC的信息;
3. 离开该环境时调用NDC.pop方法;
4. 当从一个线程中退出时调用NDC.remove方法,以便释放资源。
下面是一个模拟记录来自不同客户端请求事件的例子,代码如下:import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
public class TestNDC {
static Logger log = Logger.getLogger(TestNDC.class.getName());
public static void main(String[] args) {
log.info("Make sure %x is in your layout pattern!");
// 从客户端获得IP地址的例子
String[] ips = {"192.168.0.10","192.168.0.27"};
for (int i = 0; i{
// 将IP放进 NDC中
NDC.push(ips[i]);
log.info("A NEW client connected, who's ip should appear in this log message.");
NDC.pop();
}
NDC.remove();
log.info("Finished.");
}
}
注意配置文件中的布局格式中一定要加上%x。系统输出如下:INFO - Make sure %x is in your layout pattern!
INFO 192.168.0.10 - A NEW client connected, who's ip should appear in this log
message.
INFO 192.168.0.27 - A NEW client connected, who's ip should appear in this log
message.
INFO - Finished.
在Web应用中使用
在Web应用中,应该在哪儿对Log4J进行配置呢?首先要明确,Log4J必须在应用的其它代码执行前完成初始化。因为Servlet是在Web服务器启动时立即装入的,所以,在Web应用中一般使用一个专门的Servlet来完成Log4J的配置,并保证在web.xml的配置中,这个Servlet位于其它Servlet之前。下面是一个例子,代码如下:package org.javaresearch.log4j;
import java.io.*;
import javax.servlet.*;
import org.apache.log4j.*;
public class Log4JInit extends HttpServlet {
public void init() throws ServletException {
String prefix = getServletContext().getRealPath("/");
String file = getServletConfig().getInitParameter("log4j-config-file");
// 从Servlet参数读取log4j的配置文件
if (file != null) {
PropertyConfigurator.configure(prefix + file);
}
}
public void doGet(HttpServletRequest request,HttpServletResponse response)throws
IOException, ServletException {}
public void doPost(HttpServletRequest request,HttpServletResponse response)throws
IOException, ServletException {}
}
log4jinit
org.javaresearch. log4j.Log4JInit
log4j-config-file
/properties/log4j.properties
1
注意:上面的load-on-startup应设为1,以便在Web容器启动时即装入该Servlet。log4j.properties文件放在根的properties子目录中,也可以把它放在其它目录中。应该把.properties文件集中存放,这样方便管理。
Use log4j in J2EE
在J2EE 服务器中使用log4j包需要注意的是:1)基于J2EE服务器jar包加载机制的特殊性,必须将log4j的jar包拷贝到%JAVAHOME\jre\lib\ext\目录下。仅仅放到CLASSPATH中是不起作用的。2)因为J2EE服务器对文件操作进行了严格的控制,所以Log文件时只能写到%J2EE_HOME%\logs目录下,除非修改server.policy文件指定别的可写目录。下面我举这个很简单的例子说明一下如何在J2EE服务器中使用上log4j开发工具包,例子中涉及的文件如下:bonus.html
Bonus Calculation
ACTION="BonusAlias">
Enter social security Number:
Enter Multiplier:
上面的bonus.html接受用户输入的社会保险号和投保金额。BonusServlet.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import Beans.*;
public class BonusServlet extends HttpServlet {
CalcHome homecalc;
public void init(ServletConfig config)
throws ServletException{
//Look up home interface
try{
InitialContext ctx = new InitialContext();
Object objref = ctx.lookup("calcs");
homecalc =(CalcHome)PortableRemoteObject.narrow(
objref,CalcHome.class);
} catch (Exception NamingException) {
NamingException.printStackTrace();
}
}
public void doGet (HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {
String socsec = null;
int multiplier = 0;
double calc = 0.0;
PrintWriter out;
response.setContentType("text/html");
String title = "EJB Example";
out = response.getWriter();
out.println("");
try{
Calc theCalculation;
//Get Multiplier and Social Security Information
String strMult = request.getParameter("MULTIPLIER");
Integer integerMult = new Integer(strMult);
multiplier = integerMult.intValue();
socsec = request.getParameter("SOCSEC");
//Calculate bonus
double bonus = 100.00;
theCalculation = homecalc.create();
calc = theCalculation.calcBonus(multiplier, bonus);
} catch(Exception CreateException){
CreateException.printStackTrace();
}
//Display Data
out.println("
Bonus Calculation");
out.println("
Soc Sec: " + socsec + "
");
out.println("
Multiplier: " + multiplier + "
");
out.println("
Bonus Amount: " + calc + "
");
out.println("");
out.close();
}
public void destroy() {
System.out.println("Destroy");
}
}
BonusServlert.java得到从bonus.html中传过来的社会保险号和金额,并调用EJB程序对数据进行处理并将结果显示给客户端。
EJB 的Home接口:package Beans;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface CalcHome extends EJBHome {
Calc create() throws CreateException,RemoteException;
}
EJB 的Remote 接口:package Beans;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Calc extends EJBObject {
public double calcBonus(int multiplier,double bonus)
throws RemoteException;
}
EJB 的业务处理逻辑:package Beans;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class CalcBean implements SessionBean {
public double calcBonus(int multiplier,double bonus) {
double calc = (multiplier*bonus);
return calc;
}
//These methods are described in more
//detail in Lesson 2
public void ejbCreate() { }
public void setSessionContext(
SessionContext ctx) { }
public void ejbRemove() { }
public void ejbActivate() { }
public void ejbPassivate() { }
public void ejbLoad() { }
public void ejbStore() { }
}
成功发布后就可以在代码中加入Log4j的测试代码了。想详细了解上面的应用程序的发布步骤请叁照我最近写的一篇文章“Say ‘Hello World’ to EJB”。接下来我们在EJB中使用Log4j包(由于Servlet 中使用Log4j的方法和前面提到的例子中的使用方法相同,这里不在重复)。
1. 首先在EJB的Remote接口中增加名为logtest的方法声明:package Beans;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Calc extends EJBObject {
public double calcBonus(int multiplier,double bonus)
throws RemoteException;
public void logtest() throws RemoteException;
}
2.在 EJB的业务处理Bean中实现logtest()方法:package Beans;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import org.apache.log4j.Category;
import org.apache.log4j.Logger;
import org.apache.log4j.*;
import org.apache.log4j.NDC;
public class CalcBean implements SessionBean {
static Logger logger = Logger.getLogger(CalcBean.class.getName());
public double calcBonus(int multiplier,double bonus) {
logger.debug("Entering calcBeans() method");
logtest();
double calc = (multiplier*bonus);
logger.debug("Exitint calcBeans() method");
return calc;
}
public void logtest(){
logger.debug("Enter logtest method");
NDC.push("Client #45890");
logger.debug("Exiting logtest method.");
}
//These methods are described in more
//detail in Lesson 2
public void ejbCreate() {
PropertyConfigurator.configure("F:/example.properties");
}
public void setSessionContext(
SessionContext ctx) { }
public void ejbRemove() { }
public void ejbActivate() { }
public void ejbPassivate() { }
public void ejbLoad() { }
public void ejbStore() { }
}
这里假设log配置文件为F:盘下的example.properties文件。
接下来就是配置文件的设置问题了。Example.properties
# Attach appender A1 to root. Set root level to Level.DEBUG.
log4j.rootLogger=DEBUG, A1
# A1 is set to be a FileAppender sending its output to
# a log file. However, only error messages and above will be printed
# in A1 because A1's threshold is set to Level.ERROR.
# The fact that the root level is set to Prority.DEBUG only influences
# log requests made to the root logger. It has no influence on the
# *appenders* attached to root.
# Appender A1 writes to the file "test" in J2EE's allow write DIR ex logs direstory.
log4j.appender.A1=org.apache.log4j.FileAppender
log4j.appender.A1.File=E:/j2sdkee1.3/logs/test.log
# Truncate 'test' if it aleady exists.
log4j.appender.A1.Append=false
# Appender A1 uses the PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%5r %-5p [%t] %c{2} - %m%n
# Set the level of the logger named "Beans.CalcBean" to
# Level.DEBUG(also INFO WARN ERROR etc.),auto attach appender A1.
log4j.category.Beans.CalcBean=debug
这样重新发布这个EJB就回在%J2EE_HOME%\logs 目录下生成名为test.log的log文件。