一、问题产生
一般软件系统都会由简单到复杂,功能少的,简单的软件系统用用一般的日志输出也就能够解决问题了,但是这种软件系统真正能用的也太少了,实际场景中往往是软件么越做越复杂,越做越臃肿,且不说软件系统设计好坏,总归这是一个软件系统所要经历的,那么在系统中用日志的方式添加追踪点,帮助解决问题就成了比较常用的方法。而对于网站系统,通常除了知道这个日志是在哪个跟踪点产生的,还需要知道是由谁(往往是哪个用户,哪个 IP 等)产生的,这时就会想到往日志里塞入这类信息,但并不是每段代码都那么容易获取到用户的信息,特别是那些分层比较多,比较内层的方法,将一个对象穿透到应用的各个部分,想想都有点不好办。那么用户相关的信息每一个请求都是有办法获取到的,那有没有办法不侵入系统的内部而实现获取这些信息呢?原来 Log4j 早就为我们准备好了。
二、了解 NDC/MDC
关于 NDC/MDC 的详细介绍可以看这里:在 Web 应用中增加用户跟踪功能
简而言之:NDC是一种在同一个线程内将信息保存起来,提供日志输出时使用。MDC 与 NDC 相比,信息以 Map 方式保存,支付多个健值对。
使用方式上,NDC 通过在 PatternLayout 增加 %x 获取信息,而 MDC 通过 %X{key} 获取对应 key 的信息。 注意下,MDC 是大写 X,我在后面的试验开始时使用的小写的,结果无法正确输出
三、试着写一个
思路:通过 filter 将 session 中保存的用户信息放入 MDC,通过调整 log4j.properties 输出需要的信息。
//定义 Filter
package net.caiban.pc.erp.filter;
import net.caiban.pc.erp.config.AppConst;
import net.caiban.pc.erp.domain.SessionUser;
//import org.apache.log4j.MDC; //这里没有选择用 Log4j 的 MDC,而是换成了 slf4j
import org.slf4j.MDC;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author mays (mays@caiban.net)
*
* created on 2016-5-8
*/
public class Log4jMDCFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest rq, ServletResponse rp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request= (HttpServletRequest) rq;
HttpServletResponse response = (HttpServletResponse) rp;
try {
putMDC(request, response); //保存信息
chain.doFilter(request, response);
}finally {
clearMDC(request); //记得 clear 相关信息,否则会导致内存溢出
}
return ;
}
@Override
public void init(FilterConfig config) throws ServletException {
}
public void putMDC(HttpServletRequest request, HttpServletResponse response){
MDC.put("remoteAddr", request.getRemoteAddr());
MDC.put("remoteHost", request.getRemoteHost());
SessionUser user = (SessionUser) request.getSession().getAttribute(AppConst.SESSION_KEY);
if(user!=null){
MDC.put("uid", String.valueOf(user.getUid()));
MDC.put("cid", String.valueOf(user.getCid()));
MDC.put("account", user.getAccount());
}
}
public void clearMDC(HttpServletRequest request){
MDC.clear();
}
}
log4j 配置
#############default level and appender####################
log4j.rootCategory=info,stdout
###################appender stdout##########################
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.Threshold = debug
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m %X{uid} %X{remoteAddr}%n
关于的配置:
%X{uid} %X{remoteAddr}
随后随便找个位置打印一句日志,效果如下:
ERROR [http-nio-8080-exec-8] (ApiController.java:193) - log4j test MDC 6 127.0.0.1
大致的方法就这么些了,但实际应用中这么写还是不够的,下面看下可以做的改进
四、可以继续改进的地方
丰富信息:网站或软件系统应根据自己的需要获取请求中可以被拿到的任何信息,并将其放入 MDC,例如URI,Referer等
可以在 MDC 中将请求的时间截放入,同时可以利用过虑器统计请求耗时,最终将耗时信息提供给日志,以便输出
日志输出应重新组织下,输出日志到合适的位置,而不是像我示例那样直接在 console 中输出
另外有几点要注意:
千万记得 finally 要清除 MDC 中保存的信息
我在示例中未使用 Log4j 的 MDC,而是换成了 slf4j 的 MDC,功能没什么差别,但是 slf4j 可以直接使用 MDC.clear(),而 Log4j 的 MDC 需要一个个 remove 放入的 key。另外 slf4j 只有 MDC,没有 NDC。关于 slf4j MDC 的使用,还可以看下这篇文章(虽然文章排版实在有点乱):Slf4j MDC 使用和 基于 Logback 的实现分析
一、问题产生
一般软件系统都会由简单到复杂,功能少的,简单的软件系统用用一般的日志输出也就能够解决问题了,但是这种软件系统真正能用的也太少了,实际场景中往往是软件么越做越复杂,越做越臃肿,且不说软件系统设计好坏,总归这是一个软件系统所要经历的,那么在系统中用日志的方式添加追踪点,帮助解决问题就成了比较常用的方法。而对于网站系统,通常除了知道这个日志是在哪个跟踪点产生的,还需要知道是由谁(往往是哪个用户,哪个 IP 等)产生的,这时就会想到往日志里塞入这类信息,但并不是每段代码都那么容易获取到用户的信息,特别是那些分层比较多,比较内层的方法,将一个对象穿透到应用的各个部分,想想都有点不好办。那么用户相关的信息每一个请求都是有办法获取到的,那有没有办法不侵入系统的内部而实现获取这些信息呢?原来 Log4j 早就为我们准备好了。
二、了解 NDC/MDC
关于 NDC/MDC 的详细介绍可以看这里:在 Web 应用中增加用户跟踪功能
简而言之:NDC是一种在同一个线程内将信息保存起来,提供日志输出时使用。MDC 与 NDC 相比,信息以 Map 方式保存,支付多个健值对。
使用方式上,NDC 通过在 PatternLayout 增加 %x 获取信息,而 MDC 通过 %X{key} 获取对应 key 的信息。 注意下,MDC 是大写 X,我在后面的试验开始时使用的小写的,结果无法正确输出
三、试着写一个
思路:通过 filter 将 session 中保存的用户信息放入 MDC,通过调整 log4j.properties 输出需要的信息。
//定义 Filter
package net.caiban.pc.erp.filter;
import net.caiban.pc.erp.config.AppConst;
import net.caiban.pc.erp.domain.SessionUser;
//import org.apache.log4j.MDC; //这里没有选择用 Log4j 的 MDC,而是换成了 slf4j
import org.slf4j.MDC;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author mays (mays@caiban.net)
*
* created on 2016-5-8
*/
public class Log4jMDCFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest rq, ServletResponse rp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request= (HttpServletRequest) rq;
HttpServletResponse response = (HttpServletResponse) rp;
try {
putMDC(request, response); //保存信息
chain.doFilter(request, response);
}finally {
clearMDC(request); //记得 clear 相关信息,否则会导致内存溢出
}
return ;
}
@Override
public void init(FilterConfig config) throws ServletException {
}
public void putMDC(HttpServletRequest request, HttpServletResponse response){
MDC.put("remoteAddr", request.getRemoteAddr());
MDC.put("remoteHost", request.getRemoteHost());
SessionUser user = (SessionUser) request.getSession().getAttribute(AppConst.SESSION_KEY);
if(user!=null){
MDC.put("uid", String.valueOf(user.getUid()));
MDC.put("cid", String.valueOf(user.getCid()));
MDC.put("account", user.getAccount());
}
}
public void clearMDC(HttpServletRequest request){
MDC.clear();
}
}
log4j 配置
#############default level and appender####################
log4j.rootCategory=info,stdout
###################appender stdout##########################
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.Threshold = debug
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m %X{uid} %X{remoteAddr}%n
关于的配置:
%X{uid} %X{remoteAddr}
随后随便找个位置打印一句日志,效果如下:
ERROR [http-nio-8080-exec-8] (ApiController.java:193) - log4j test MDC 6 127.0.0.1
大致的方法就这么些了,但实际应用中这么写还是不够的,下面看下可以做的改进
四、可以继续改进的地方
丰富信息:网站或软件系统应根据自己的需要获取请求中可以被拿到的任何信息,并将其放入 MDC,例如URI,Referer等
可以在 MDC 中将请求的时间截放入,同时可以利用过虑器统计请求耗时,最终将耗时信息提供给日志,以便输出
日志输出应重新组织下,输出日志到合适的位置,而不是像我示例那样直接在 console 中输出
另外有几点要注意:
千万记得 finally 要清除 MDC 中保存的信息
我在示例中未使用 Log4j 的 MDC,而是换成了 slf4j 的 MDC,功能没什么差别,但是 slf4j 可以直接使用 MDC.clear(),而 Log4j 的 MDC 需要一个个 remove 放入的 key。另外 slf4j 只有 MDC,没有 NDC。关于 slf4j MDC 的使用,还可以看下这篇文章(虽然文章排版实在有点乱):Slf4j MDC 使用和 基于 Logback 的实现分析