线程维度
当我们的项目中同一时间打印的日志较多,例如同一时间有5个线程在执行,每个线程分别要打出1到20这几个数字,这样三个线程的日志就会互相穿插,除了系统自动生成的线程标识,根本不能清晰地看出具体的单个线程的执行流程。假入一个线程在执行过程中出现异常中断且不打印异常,在大量日志中就很难判断该线程在哪里中断。而系统自动生成的线程标识这东西,在实际的生产中,基本不可能用这玩意儿。所以得有另外一个方法来标记线程。
解决办法如下:
用一个系统日志类继承org.slf4j.Logger,实现所有接口方法,在所有打印的日志之前加上标记。假设我们的系统日志类叫THLogger:
public class THLogger implements org.slf4j.Logger {
private org.slf4j.Logger logger;
private THLogger (Class clazz) {
logger = LoggerFactory.getLogger(clazz);
}
public static org.slf4j.Logger getLogger(Class clazz) {
return new THLogger (clazz);
}
public String getLogIdMsg() {
return "logTraceId[" + getUUID() + "]--";
}
private String getUUID() {
return LoggerHandler.getUUID().get();
}
@Override
public String getName() {
return logger.getName();
}
@Override
public boolean isTraceEnabled() {
return logger.isTraceEnabled();
}
@Override
public void trace(String msg) {
logger.trace(getLogIdMsg() + msg);
…
}
在打印日志的时候就用THLogger的logger对象来打印,即定义logger变量如下:private static final Logger logger = THLogger.getLogger(xxx.class)
那么线程标记生成如下:
public class LoggerHandler {
private static ThreadLocal<String> logTraceId = new ThreadLocal<String>();
public static ThreadLocal<String> getUUID() {
if (null == logTraceId.get()) {
genUUID();
}
return logTraceId;
}
private static void genUUID() {
String s = UUID.randomUUID().toString();
//去掉“-”符号
logTraceId.set(s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24));
}
}
ThreadLocal为每一个线程隔离了一个uuid,打印日志的时候会调用LoggerHandler.getUUID().get()来获取当前线程的uuid标记,而有返回值的getUUID()方法会先判断当前线程的uuid是否为空,为空则为当前线程中生成一个uuid,不为空则从threadLocal容器logTraceId中取出当前线程的uuid。这样就做到了不同线程打印日志时会带上不同的uuid。
这就是以线程为维度的日志标记方法,但是以上方法有一个问题存在,那就是从threadLocal中取出当前线程的uuid,也就是通过logTraceId.get()获取当前线程的uuid时,会根据系统默认生成的线程名来做当前线程的判断。这个是什么意思呢,举个栗子:假如系统生成一个线程叫http-bio-8088-exec-1,第二个线程叫http-bio-8088-exec-2,依次类推,直到系统默认的最大值。然后线程1跑一段时间之后销毁了。接下来系统将会重新利用http-bio-8088-exec-1来命名新的线程。也就是说系统的默认命名方式是重复利用的。因此第二个http-bio-8088-exec-1线程还会继续用第一个http-bio-8088-exec-1的标记,因为取uuid的时候会默认这两个线程是同一个。
接口维度
将LoggerHandler稍微改造一下,将其作为一个过滤器:
public class LogFilter extends OncePerRequestFilter {
private static ThreadLocal<String> logTraceId = new ThreadLocal<String>();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
genUUID();
filterChain.doFilter(request, response);
}
public static ThreadLocal<String> getUUID() {
if (null == logTraceId.get()) {
genUUID();
}
return logTraceId;
}
private static void genUUID() {
String s = UUID.randomUUID().toString();
//去掉“-”符号
logTraceId.set(s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24));
}
}
这样每次接口请求都会执行过滤方法,先为当前线程生成uuid保存到threadlocal中。这样就算第二个http-bio-8088-exec-1线程启动了,也会生成不同的uuid。此时每个接口都有一个uuid标记,标识一次接口请求中打印的全部日志。