Log4j分层次记录到MySQL数据库和HTML等文件

Log4j分层次记录到MySQL数据库和HTML等文件

本文关键点:

  • Log4j分层次分别记录到不同文件或数据库
  • 使用MySQL数据库
  • 使用Filter实现向数据库中插入日志时记录用户ID
  • 自定义HTMLLayout格式

基本原理

Log4j的分层记录原理

log4j采用类似Java包的方式进行层次化日志记录,一般情况下我们用如下代码获得logger对象

private Logger logger = Logger.getLogger(CLASS.name);

如上 CLASS.name 包含与包相关的信息,例如 com.liucc.Test 类,那么该logger记录的日志会记录到所有包含 com.liucc.Test 的Logger中,假如我们定义了一个名为”com.liucc”的 Logger,那么日志信息同样会记录到该Logger的appender中。

log4j.rootLogger = WARN,Console,OneFileAll
log4j.logger.agv = DEBUG,SYSTEMLOG
log4j.logger.agv.user = INFO,DB,USERLOG

如上,所有 logger 的信息都会记录到 rootLogger 中,且 log4j.logger.agv.user 中的信息同时会记录到 log4j.logger.agv 中。这样我们就能很方便的控制日志,例如我希望系统日志记录所有信息,而用户日志只记录需要给普通用户看的信息。

给log4j新增字段的原理

默认情况下,log4j只能记录类、时间、方法、信息等少量字段,如果我们希望在记录日志的时候将用户ID等信息记录进去就需要用到log4j的MDC(Mapped Diagnostic Context)类。

MDC.put("userId_log4j",user.getUserId());

如上所示我们可以增加一个 userId_log4j 字段用于记录用户ID,然后通过 “%X{userId_log4j}” 即可用到配置文件中,和”%m”等用法相同。
在开发WEB APP时,为了方便的将用户ID加入到MDC中,一般通过Filter插入这些字段,这样就不需要每次记录日志时单独操作了。

记录到MySQL数据库原理

只需配置 appender 为 org.apache.log4j.jdbc.JDBCAppender 并配置好与数据库相关的url,username,password 等信息即可,成功之后回头看还是挺简单的,但实际操作中却花了很多时间,一方面是为了增加用户ID字段,不理解filter到底该怎么配,一直认为是配置log4j的filter,后来才发现其实是web框架中的filter,这搞定后就几乎没有什么困难了。

自定义log4j HTMLLayout样式原理

继承HTMLLayout类并重写其中的有关方法,就可以自定义HTML样式,并把该类用到配置文件中即可

具体例子

log4j.properties

log4j.rootLogger=WARN,Console,OneFileAll
log4j.logger.agv = DEBUG,SYSTEMLOG
log4j.logger.agv.user = INFO,DB,USERLOG


log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.OneFileAll.Threshold=WARN
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n  

log4j.appender.OneFileAll=org.apache.log4j.RollingFileAppender 
log4j.appender.OneFileAll.File=D://AGVLOG/all.log
log4j.appender.OneFileAll.MaxFileSize=50MB
log4j.appender.OneFileAll.Threshold=DEBUG
log4j.appender.OneFileAll.layout=org.apache.log4j.PatternLayout
log4j.appender.OneFileAll.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n 

log4j.appender.SYSTEMLOG=org.apache.log4j.RollingFileAppender 
log4j.appender.SYSTEMLOG.File=D://AGVLOG/system.log
log4j.appender.SYSTEMLOG.MaxFileSize=50MB
log4j.appender.SYSTEMLOG.Threshold=DEBUG
log4j.appender.SYSTEMLOG.layout=org.apache.log4j.PatternLayout
log4j.appender.SYSTEMLOG.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n 

log4j.appender.USERLOG=org.apache.log4j.RollingFileAppender
log4j.appender.USERLOG.file=D://AGVLOG/user_log.html
log4j.appender.USERLOG.layout=agv.log4j.MyHTMLLayout
log4j.appender.FILE.layout.LocationInfo=true
log4j.appender.USERLOG.MaxFileSize=5MB
log4j.appender.USERLOG.Threshold=INFO

log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DB.BufferSize = 1
log4j.appender.DB.driver=com.mysql.jdbc.Driver
log4j.appender.DB.URL=jdbc:mysql://localhost:3306/zyb_agv?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
log4j.appender.DB.user=vga
log4j.appender.DB.password=123456
log4j.appender.DB.sql=insert  into tb_log (userId,class,method,createDate,logLevel, msg) values ('%X{userId_log4j}','%C','%M','%d{yyyy-MM-dd HH:mm:ss}','%p','%m'); 

该文件中定义了三个logger,分别是root,agv 和 agv.user,agv是我的项目的根目录,由于使用了spring,root会将spring的调试信息一并记录,量非常大,而agv只记录与我项目相关的日志,方便查看。agv.user 记录需要在界面中展示给用户看的日志,这些日志必然更加精简。
其中agv.user有两个appender,分别是DB和USERLOG,其中DB是记录到本项目的数据库,USERLOG记录到html文件,方便查看。

agv.filter.LoggerFilter.java

package agv.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.log4j.MDC;
import agv.pageModel.User;
import agv.web.WebContant;

public class LoggerFilter implements Filter{

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req=(HttpServletRequest)request;
        HttpSession session= req.getSession();
        if (session==null){
            MDC.put("userId_log4j",0);  
        }
        else{
            try{
                MDC.put("userId_log4j",0);
                User user=(User)WebContant.getCurrentSessionInfo(req).getUser();
                if (user!=null){
                    MDC.put("userId_log4j",user.getUserId());
                }
            }catch(Exception e){
                System.out.println(e.getMessage());
            }
        }

       chain.doFilter(request, response);

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

}

实现其中的doFilter方法即可,在其中去除request中的用户信息通过MDC存储下来供log4j使用,由于request中可能会不含user,因此通过 try…catch 防止程序崩溃,其中userId=0是系统用户。

web.xml

<filter>
    <filter-name>log4jMDNFilter</filter-name>
    <filter-class>agv.filter.LoggerFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>log4jMDNFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping> 

<context-param>  
    <param-name>log4jConfigLocation</param-name>  
    <param-value>classpath:log4j.properties</param-value>  
    <!-- <param-value>classpath:log4j.xml</param-value> -->
</context-param>  
<context-param>    
    <param-name>log4jRefreshInterval</param-name>    
       <param-value>3000</param-value>    
</context-param>   
<listener>  
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
</listener>  

如果是WEB APP开发,那么需要在web.xml文件中配置之前写的Filter和log4j有关信息。

agv.log4j.MyHTMLLayout.java

package agv.log4j;
import java.text.SimpleDateFormat;   
import org.apache.log4j.HTMLLayout;   
import org.apache.log4j.Layout;   
import org.apache.log4j.Level;   
import org.apache.log4j.helpers.Transform;   
import org.apache.log4j.spi.LocationInfo;   
import org.apache.log4j.spi.LoggingEvent; 

public class MyHTMLLayout extends HTMLLayout{


    public MyHTMLLayout() {   
    }   

    protected final int BUF_SIZE = 256;   

    protected final int MAX_CAPACITY = 1024;   

    static String TRACE_PREFIX = "<br>&nbsp;&nbsp;&nbsp;&nbsp;";   

    private StringBuffer sbuf = new StringBuffer(BUF_SIZE);   

    String title="AGV日志";   

    /**  
     * A string constant used in naming the option for setting the the HTML  
     * document title. Current value of this string constant is <b>Title</b>.  
     */  
    public static final String TITLE_OPTION = "Title";   

    // Print no location info by default   
    boolean locationInfo = true;   

    public String format(LoggingEvent event) {   

        if (sbuf.capacity() > MAX_CAPACITY) {   
            sbuf = new StringBuffer(BUF_SIZE);   
        } else {   
            sbuf.setLength(0);   
        }   
        sbuf.append(Layout.LINE_SEP + "<tr>" + Layout.LINE_SEP);   

        sbuf.append("<td title='执行时间'>");   
        sbuf.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));   
        sbuf.append("</td>" + Layout.LINE_SEP);   

        sbuf.append("<td title='级别'>");   
        if (event.getLevel().equals(Level.FATAL)) {   
            sbuf.append("<font color=\"#339933\">");   
            sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));   
            sbuf.append("</font>");   
        } else if (event.getLevel().isGreaterOrEqual(Level.WARN)) {   
            sbuf.append("<font color=\"#993300\"><strong>");   
            sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));   
            sbuf.append("</strong></font>");   
        } else {   
            sbuf.append("<font color=\"green\">");   
            sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));   
            sbuf.append("</font>");   
        }   
        sbuf.append("</td>" + Layout.LINE_SEP);   

        if (locationInfo) {   
            LocationInfo locInfo = event.getLocationInformation();   
            sbuf.append("<td title='所在行'>");  
            sbuf.append(Transform.escapeTags(locInfo.getFileName()));  
            sbuf.append(':');  
            sbuf.append(locInfo.getLineNumber());  
            sbuf.append("</td>" + Layout.LINE_SEP);  
        }  


        sbuf.append("<td title='信息'>");   
        sbuf.append(Transform.escapeTags(event.getRenderedMessage()));   
        sbuf.append("</td>" + Layout.LINE_SEP);   
        sbuf.append("</tr>" + Layout.LINE_SEP);   

        if (event.getNDC() != null) {   
            sbuf.append("<tr><td bgcolor=\"#EEEEEE\" style=\"font-size : xx-small;\" colspan=\"6\" title=\"Nested Diagnostic Context\">");   
            sbuf.append("NDC: " + Transform.escapeTags(event.getNDC()));   
            sbuf.append("</td></tr>" + Layout.LINE_SEP);   
        }   

        String[] s = event.getThrowableStrRep();   
        if (s != null) {   
            sbuf.append("<tr><td bgcolor=\"#993300\" style=\"color:White; font-size : xx-small;\" colspan=\"4\">");   
            appendThrowableAsHTML(s, sbuf);   
            sbuf.append("</td></tr>" + Layout.LINE_SEP);   
        }   
        return sbuf.toString();   
    }   

    private void appendThrowableAsHTML(String[] s, StringBuffer sbuf) {   
        if (s != null) {   
            int len = s.length;   
            if (len == 0)   
                return;   
            sbuf.append(Transform.escapeTags(s[0]));   
            sbuf.append(Layout.LINE_SEP);   
            for (int i = 1; i < len; i++) {   
                sbuf.append(TRACE_PREFIX);   
                sbuf.append(Transform.escapeTags(s[i]));   
                sbuf.append(Layout.LINE_SEP);   
            }   
        }   
    }   

    /**  
     * Returns appropriate HTML headers.  
     */  
    public String getHeader() {   

        StringBuffer sbuf = new StringBuffer();   

        sbuf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">" + Layout.LINE_SEP);   
        sbuf.append("<html>" + Layout.LINE_SEP);   
        sbuf.append("<head>" + Layout.LINE_SEP);   

        sbuf.append("<title>" + title + "</title>" + Layout.LINE_SEP);   
        sbuf.append("<style type=\"text/css\">" + Layout.LINE_SEP);   
        sbuf.append("<!--" + Layout.LINE_SEP);   
        sbuf.append("body, table {font-family: '雅黑',arial,sans-serif; font-size: 12px;}" + Layout.LINE_SEP);   
        sbuf.append("th {background: #336699; color: #FFFFFF; text-align: left;}" + Layout.LINE_SEP);   
        sbuf.append("-->" + Layout.LINE_SEP);   
        sbuf.append("</style>" + Layout.LINE_SEP);   
        sbuf.append("</head>" + Layout.LINE_SEP);   
        sbuf.append("<body bgcolor=\"#FFFFFF\" topmargin=\"6\" leftmargin=\"6\">" + Layout.LINE_SEP);   

        sbuf.append("<table cellspacing=\"0\" cellpadding=\"4\" border=\"1\" bordercolor=\"#224466\" width=\"100%\">" + Layout.LINE_SEP);   
        sbuf.append("<tr>" + Layout.LINE_SEP);   

        sbuf.append("<th>执行时间</th>" + Layout.LINE_SEP);   
        sbuf.append("<th>级别</th>" + Layout.LINE_SEP);   

        if (locationInfo) {   
            sbuf.append("<th>所在行</th>" + Layout.LINE_SEP);   
        }   

        sbuf.append("<th>信息</th>" + Layout.LINE_SEP);   
        sbuf.append("</tr>" + Layout.LINE_SEP);   
        sbuf.append("<br></br>" + Layout.LINE_SEP);  
        return sbuf.toString();   
    }   
}

最终结果

自定义HTML

HTML

数据库截图

DB

发布了22 篇原创文章 · 获赞 21 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/liucc09/article/details/51439812
今日推荐