需求:总客户和分客户登陆日志,要求目录结构
logs
|
|-- login.log
|-- A000008
| |-- login.log
|-- A000009
|-- login.log
分析:总客户的日志比较好做,配置一下单独的输出文件就可以,配置见
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- Console output -->
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss} %m (%F:%L) \n" />
</layout>
</appender>
<!--全局登陆日志-->
<appender name="allClientLoginAppender" class="org.apache.log4j.FileAppender">
<param name="file" value="../logs/login.log" />
<param name="append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{yyyy-MM-dd HH:mm:ss} %m \n" />
</layout>
</appender>
<category name="java.sql">
<priority value="debug"/>
</category>
<logger name="com.lizp.framework.interceptor.LoginInterceptor">
<appender-ref ref="allClientLoginAppender"/>
</logger>
<root>
<priority value="info"/>
<appender-ref ref="STDOUT"/>
</root>
</log4j:configuration>
难点:输出日志中检出含clientNo并输出到指定clientNo下面的文件夹下,呵呵,大家先考虑一下。
—————————————————————————————————————————————————————————————
分客户的肯定与总的日志相差不大,只是多了一个过滤功能。只好先看下log4j中FileAppender了
FileAppender类
FileAppender继承自WriterAppender,它将日志写入文件。主要的日志写入逻辑已经在WriterAppender中处 理,FileAppender主要处理的逻辑主要在于将设置日志输出文件名,并通过设置的文件构建WriterAppender中的 QuiteWriter字段实例。如果Log文件的目录没有创建,在setFile()方法中会先创建目录,再设置日志文件。另外,所有 FileAppender字段在调用activateOptions()方法中生效。
(上述解析来自http://www.cnblogs.com/duanxz/archive/2013/02/01/2889500.html)
WriterAppender又依赖于AppenderSkeleton,里面的这个方法是写入文件的,注意中间有个Filter,过滤字符
/**
* This method performs threshold checks and invokes filters before
* delegating actual logging to the subclasses specific {@link
* AppenderSkeleton#append} method.
* */
public
synchronized
void doAppend(LoggingEvent event) {
if(closed) {
LogLog.error("Attempted to append to closed appender named ["+name+"].");
return;
}
if(!isAsSevereAsThreshold(event.getLevel())) {
return;
}
Filter f = this.headFilter;
FILTER_LOOP:
while(f != null) {
switch(f.decide(event)) {
case Filter.DENY: return;
case Filter.ACCEPT: break FILTER_LOOP;
case Filter.NEUTRAL: f = f.getNext();
}
}
this.append(event);
}
在org.apache.log4j.varia下发现StringMatchFilter,匹配相应的字符并过滤到,与需求相近了(这里我的想法是不可能要把其他的clientNo都匹配过滤吧,也得想法改写了)
/**
Returns {@link Filter#NEUTRAL} is there is no string match.
*/
public
int decide(LoggingEvent event) {
String msg = event.getRenderedMessage();
if(msg == null || stringToMatch == null)
return Filter.NEUTRAL;
if( msg.indexOf(stringToMatch) == -1 ) {
return Filter.NEUTRAL;
} else { // we've got a match
if(acceptOnMatch) {
return Filter.ACCEPT;
} else {
return Filter.DENY;
}
}
而这里我是要抓取字符串,也就是把if( msg.indexOf(stringToMatch) != -1 ) { // 更改为只接受对应的字符串,而原来的是只过滤到相应的字符串
public class StringCatchFilter extends StringMatchFilter {
/**
@deprecated Options are now handled using the JavaBeans paradigm.
This constant is not longer needed and will be removed in the
<em>near</em> term.
*/
public static final String STRING_TO_MATCH_OPTION = "StringToMatch";
/**
@deprecated Options are now handled using the JavaBeans paradigm.
This constant is not longer needed and will be removed in the
<em>near</em> term.
*/
public static final String ACCEPT_ON_MATCH_OPTION = "AcceptOnMatch";
boolean acceptOnMatch = true;
String stringToMatch;
/**
Returns {@link Filter#NEUTRAL} is there is no string match.
*/
public
int decide(LoggingEvent event) {
String msg = event.getRenderedMessage();
if(msg == null || stringToMatch == null)
return Filter.NEUTRAL;
if( msg.indexOf(stringToMatch) != -1 ) { // 更改为只接受对应的字符串,而原来的是只过滤到相应的字符串
return Filter.NEUTRAL;
} else { // we've got a match
if(acceptOnMatch) {
return Filter.ACCEPT;
} else {
return Filter.DENY;
}
}
}
}
恩,过滤器符合要求了,余下的任务就是生成文件了。
创建一个FileAppender继承原来的FileAppender重写一下doAppend就可以了
public
synchronized
void doAppend(LoggingEvent event) {
if(closed) {
LogLog.error("Attempted to append to closed appender named ["+name+"].");
return;
}
if(!isAsSevereAsThreshold(event.getLevel())) {
return;
}
Filter f = this.headFilter;
Subject currentUser = SecurityUtils.getSubject(); // 获取到session中clientNo的值
Session session = currentUser.getSession();
if(null != session){
String clientCode = (String) session.getAttribute(Constants.SESSION_CLIENTNO);
if(!StringUtils.isEmpty(clientCode)){
FILTER_LOOP:
while(f != null) {
if(f instanceof StringCatchFilter){
StringCatchFilter ff = (StringCatchFilter)f;
ff.setStringToMatch(clientCode); // 动态设置参数拦截指定的clientNo下的登陆信息
}
switch(f.decide(event)) {
case Filter.DENY: return;
case Filter.ACCEPT: break FILTER_LOOP;
case Filter.NEUTRAL: f = f.getNext();
}
}
try {
this.setFile("../logs/"+clientCode+"/login.log",fileAppend,bufferedIO,bufferSize); // 动态设置拦截日志的输出路径
} catch (IOException e) {
e.printStackTrace();
}
}
this.append(event); // 追加日志,包含登出error和登陆info
}
}
拦截到session中的clientNo并决定输出到那个客户下的日志文件中
当然还需要配置下log4j.xml
<!--分客户登陆日志-->
<appender name="eachClientLoginAppender" class="com.lizp.util.FileAppender">
<!-- <param name="file" value="../logs/login.log" /> -->
<param name="encoding" value="UTF-8" />
<param name="append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{yyyy-MM-dd HH:mm:ss} %m \n" />
</layout>
<filter class="com.lizp.util.StringCatchFilter">
<!-- <param name="StringToMatch" value="A0000001" /> -->
<param name="AcceptOnMatch" value="false" />
</filter>
</appender>
然后就可以看到目录结构和生成日志了