这个类是 Web 日志实现的关键类之一,它继承至 Writer,实际上是一组 Writer 的集合,其中包含至少一个默认 Writer 将数据输出至控制台,另包含零至若干个由 Queue
我们在 Log4j.xml 中修改一下配置,将 Appender 切换为 WebLogAppender,那对 Log4j 本身的扩展就算完成了:
清单 3:Log4j.xml 配置
<appender name="CONSOLE" class="org.fenixsoft.log.WebLogAppender">
<param name="Threshold" value="DEBUG"/>
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message\n -->
<param name="ConversionPattern" value="%d %p [%c] %m%n"/>
</layout>
</appender>
<param name="Threshold" value="DEBUG"/>
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message\n -->
<param name="ConversionPattern" value="%d %p [%c] %m%n"/>
</layout>
</appender>
接着,建立一个支持异步的 Servlet,目的是每个访问这个 Servlet 的客户端,都在 ASYNC_CONTEXT_QUEUE 中注册一个异步上下文对象,这样当有 Logger 信息发生时,就会输出到这些客户端。同时,将建立一个针对这个异步上下文对象的监听器,当产生超时、错误等事件时,将此上下文从队列中移除。
清单 4:Web 日志注册 Servlet
/**
* Servlet implementation class WebLogServlet
*/
@WebServlet(urlPatterns = { "/WebLogServlet" }, asyncSupported = true)
public class WebLogServlet extends HttpServlet {
/**
* serialVersionUID
*/
private static final long serialVersionUID = -260157400324419618L;
/**
* 将客户端注册到监听 Logger 的消息队列中
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html;charset=UTF-8");
res.setHeader("Cache-Control", "private");
res.setHeader("Pragma", "no-cache");
req.setCharacterEncoding("UTF-8");
PrintWriter writer = res.getWriter();
// for IE
writer.println("<!-- Comet is a programming technique that enables web
servers to send data to the client without having any need for the client
to request it. -->\n");
writer.flush();
final AsyncContext ac = req.startAsync();
ac.setTimeout(10 * 60 * 1000);
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
WebLogAppender.ASYNC_CONTEXT_QUEUE.remove(ac);
}
public void onTimeout(AsyncEvent event) throws IOException {
WebLogAppender.ASYNC_CONTEXT_QUEUE.remove(ac);
}
public void onError(AsyncEvent event) throws IOException {
WebLogAppender.ASYNC_CONTEXT_QUEUE.remove(ac);
}
public void onStartAsync(AsyncEvent event) throws IOException {
}
});
WebLogAppender.ASYNC_CONTEXT_QUEUE.add(ac);
}
}
* Servlet implementation class WebLogServlet
*/
@WebServlet(urlPatterns = { "/WebLogServlet" }, asyncSupported = true)
public class WebLogServlet extends HttpServlet {
/**
* serialVersionUID
*/
private static final long serialVersionUID = -260157400324419618L;
/**
* 将客户端注册到监听 Logger 的消息队列中
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html;charset=UTF-8");
res.setHeader("Cache-Control", "private");
res.setHeader("Pragma", "no-cache");
req.setCharacterEncoding("UTF-8");
PrintWriter writer = res.getWriter();
// for IE
writer.println("<!-- Comet is a programming technique that enables web
servers to send data to the client without having any need for the client
to request it. -->\n");
writer.flush();
final AsyncContext ac = req.startAsync();
ac.setTimeout(10 * 60 * 1000);
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
WebLogAppender.ASYNC_CONTEXT_QUEUE.remove(ac);
}
public void onTimeout(AsyncEvent event) throws IOException {
WebLogAppender.ASYNC_CONTEXT_QUEUE.remove(ac);
}
public void onError(AsyncEvent event) throws IOException {
WebLogAppender.ASYNC_CONTEXT_QUEUE.remove(ac);
}
public void onStartAsync(AsyncEvent event) throws IOException {
}
});
WebLogAppender.ASYNC_CONTEXT_QUEUE.add(ac);
}
}
服务端处理到此为止差不多就结束了,我们再看看客户端的实现。其实客户端我们直接访问这个 Servlet 就可以看到浏览器不断的有日志输出,并且这个页面的滚动条会一直持续,显示 http 连接并没有关闭。为了显示,我们还是对客户端进行了包装,通过一个隐藏的 frame 去读取 WebLogServlet 发出的信息,既 Comet 流方式实现。