论坛首页 Java企业应用论坛

关于日志的设计模式讨论

浏览 7137 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-10-12  
使用log4j打印日志的遗留问题:
背景:
   1.有很多用户同时使用的应用软件;日志是按时间顺序打印出来的;不同用户的日志是交叉在一起;
     在日志文件里一般很难区分出专属于某个用户的日志;
     如果应用软件出现问题,拿到现场的日志,很难分析;
   2.用户操作可能是一个流程
      比如:登陆-->访问自服务-->导出报表
      现在如何在日志文件中找到某个用户的流程日志?
抽象的日志模型:
    1.线程日志:定义为用户操作某个功能后打印出的相关日志;日志可能在A.class,B.class打印;
    2.流程日志:多个线程组成一个流程,由这几个线程打印出的日志;
解决访问:
    1.线程日志:这个比较容易解决,为每个线程生成一个线程号即可;
    2.流程日志:在不改变原先应用软件模型的基础上,暂时没想到好的解决办法;
              有想到能否在应用软件级别中,实现类似服务器中的session的管理,但不知道session如何管理,log4j又怎么知道用户的session;
              在可以改变应用软件模型的基础上:
                  建议用request.session.sessionId,也就是保证在调用log4j打印日志的时候,得到这个sessionId,并将sessionId打印出来;

抛庄引...    
         
  
   发表时间:2007-10-12  
已经找到解决方案了
   -------------------------------------------------------
   最终实现:用户登陆系统后,能捕获到跟该用户相关的所有的日志;
   用户访问系统输出日志如下:
       [3*99N1192177074953 ahau205109] 2007-10-12 16:20:08  用户登陆
       [3*99N1192177074953 ahau205109] 2007-10-12 16:20:09  检查用户密码有效性
       [3*99N1192177074953 ahau205109] 2007-10-12 16:20:10  登陆成功
    用户登陆系统后,访问某个功能页面,输出日志如下:       
       [3*99N1192177074953 ahau205109] 2007-10-12 16:20:18  导出报表[财务]
       [3*99N1192177074953 ahau205109] 2007-10-12 16:20:28  导出报表[成功]
 
整个日志文件内容:
       [3*99N1192177074953 ahau205109] 2007-10-12 16:20:08  用户登陆
        [3*99N1192177074953 ahau205109] 2007-10-12 16:20:09  检查用户密码有效性
        [3*99N1192177074954 武功] 2007-10-12 16:20:10  用户登陆
        [3*99N1192177074953 ahau205109] 2007-10-12 16:20:12  登陆成功
        [3*99N1192177074954 武功] 2007-10-12 16:20:16  检查用户密码有效性
        [3*99N1192177074954 武功] 2007-10-12 16:20:18  登陆成功
       [3*99N1192177074953 ahau205109] 2007-10-12 16:20:20  导出报表[财务]
       [3*99N1192177074953 ahau205109] 2007-10-12 16:20:22  导出报表[成功]
       [3*99N1192177074954 武功] 2007-10-12 16:20:25  添加操作员
        [3*99N1192177074954 武功] 2007-10-12 16:20:30  添加成功
根据流程标志:比如 [3*99N1192177074953 ahau205109], [3*99N1192177074954 武功]
       就能很容易的从日志文件分析到日志了;


实现过程:
   1.扩展log4j的
      1) LogginEvent:增加字段及构造函数
        String flow ; //[流程标志号]
     2) Category:增加方法
         在原先的log,error,info,debug等方法中增加参数,String flow
     3) ConsoleAppender 扩展
        重写方法:   protected void subAppend(LoggingEvent event)
          1. 将event的flow标志缓存起来
           2. 如果缓存不存在flow的流程号,则建立之
            3. 维护缓存更新策略
           4.定义输出日志格式,增加流程号
 
0 请登录后投票
   发表时间:2007-10-13  
给个源代码来看看
0 请登录后投票
   发表时间:2007-10-13  
又做了个扩展,无需修改log4j源代码,也无需修改原先所有的业务逻辑代码;做成插件模式

1.添加类
package log;

public class LoggingFlow {
    private LoggingFlow() {}

    public LoggingFlow(Object flow) {
        this.flow = flow;
    }

    private Object flow;
    public Object getFlow() {
        return flow;
    }

    public void setFlow(Object flow) {
        this.flow = flow;
    }
}

package log;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.Layout;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class ConsoleAppenderEx extends ConsoleAppender {
    ThreadLocal currentThread = new ThreadLocal();
    static String TA = "[TA]"; //事务ID
    /**
     * [TA] 的生成规则 Thread:seq0000001;User:user01;
     ***/
    protected void subAppend(LoggingEvent event) {
        if (event.getMessage() instanceof LoggingFlow) {
            LoggingFlow flow = (LoggingFlow) event.getMessage();
            if (flow.getFlow() != null &&
                flow.getFlow() instanceof HttpServletRequest) {
                init((HttpServletRequest) flow.getFlow());
            } else if (flow.getFlow() != null) {
                init(flow.getFlow());
            }
            return;
        }
        String flow = String.valueOf(currentThread.get() == null ? "System" :
                                     currentThread.get());
        //flow = StringUtils.rightPad(flow, 40);
        String message = StringUtils.replace(super.layout.format(event),
                                             ConsoleAppenderEx.TA,
                                             flow);
        qw.write(message);
        if (super.layout.ignoresThrowable()) {
            String s[] = event.getThrowableStrRep();
            if (s != null) {
                int len = s.length;
                for (int i = 0; i < len; i++) {
                    qw.write(s[i]);
                    qw.write(Layout.LINE_SEP);
                }
            }
        }
        if (immediateFlush) {
            qw.flush();
        }
    }
   
    public Object getUser(HttpServletRequest request){
        //自己实现...
        return null;
    }
    private void init(HttpServletRequest request) {
        Object obj = null;
        //从request获取用户信息
        obj = getUser(request);
        //同时记录用户所属的ip,如果不存在用户,则记录ip
         if (obj == null) {
                    obj = request.getRemoteAddr();
                } else {
                    if (!request.getRemoteAddr().equalsIgnoreCase(obj.toString())) {
                        obj = request.getRemoteAddr() + " " + obj;
                    }
         }        init(obj);
    }

    private void init(Object object) {
        Object obj = currentThread.get();
        if (object != null) {
            obj = object;
        }
        currentThread.set(obj);
    }
}

package log;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;

public class LoggingFilter implements Filter {
    /**
     * Called by the web container to indicate to a filter that it is being taken out of service.
     *
     * @todo Implement this javax.servlet.Filter method
     */
    public void destroy() {
    }

    /**
     * The <code>doFilter</code> method of the Filter is called by the container each time a request/response pair is
     * passed through the chain due to a client request for a resource at the end of the chain.
     *
     * @param request ServletRequest
     * @param response ServletResponse
     * @param chain FilterChain
     * @throws IOException
     * @throws ServletException
     * @todo Implement this javax.servlet.Filter method
     */
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException,
            ServletException {
        org.apache.log4j.Logger.getInstance(this.getClass()).error(new log.LoggingFlow((HttpServletRequest)request));
        chain.doFilter(request,response);
    }

    /**
     * Called by the web container to indicate to a filter that it is being placed into service.
     *
     * @param filterConfig FilterConfig
     * @throws ServletException
     * @todo Implement this javax.servlet.Filter method
     */
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

2.修改web.xml
  <filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>vas.base.common.log.LoggingFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>*.do</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>*.jsp</url-pattern>
  </filter-mapping>
3.修改log4j.properties 的appender配置
#CONSOLE appender org.apache.log4j.ConsoleAppenderEx
log4j.appender.CONSOLE=log.ConsoleAppenderEx
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[[TA]] [%t] %d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n

ok,看看代码运行效果吧
如果用户事先没登陆,则用用户的ip来访问日志;
用户登陆成功后,用用户的ip加用户的标志来访问日志


0 请登录后投票
   发表时间:2007-10-15  
建议看一下log4j的NDC或者MDC。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics