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> ";
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();
}
}