【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
1.背景简述
依赖原始的log4j2配置,很难从某服务庞杂的日志中,单独找寻出某次API调用的全部日志。
本文通过在日志中打印唯一的traceId来实现每次调用的追踪。
2.关键思路
2.1.MDC
日志追踪目标是每次请求级别的,也就是说同一个接口的每次请求,都应该有不同的traceId。
每次接口请求,都是一个单独的线程,所以自然我们很容易考虑到通过ThreadLocal实现上述需求。
考虑到log4j本身已经提供了类似的功能MDC,所以直接使用MDC进行实现。
关于MDC的简述
Mapped Diagnostic Context,即:映射诊断环境。
MDC是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。
MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。
关于MDC的关键操作
向MDC中设置值:MDC.put(key, value);
从MDC中取值:MDC.get(key);
将MDC中内容打印到日志中:%X{key}
2.2.自定义Filter
假定已经解决了traceId的存放问题,那么何时进行traceId的存放呢?其实有多重实现思路,例如:过滤器、AOP、拦截器等等。
本文采用过滤器的形式,即:自定义一个Filter,继承自GenericFilterBean。
其他实现方式可自行探索。
package com.importer.api.filter;
import com.importer.common.constant.ImporterConstant;
import com.importer.common.util.TraceIdUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by zhanghuan on 2019/12/11.
*/
@WebFilter(urlPatterns = "/*", filterName = "logMdcFilter")
@Order(1)
@Component
public class LogMdcFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//traceId初始化
initTraceId((HttpServletRequest) servletRequest);
//执行后续过滤器
filterChain.doFilter(servletRequest,servletResponse);
}
/**
* traceId初始化
*/
private void initTraceId(HttpServletRequest request) {
//尝试获取http请求中的traceId
String traceId = request.getParameter(ImporterConstant.TRACE_ID);
//如果当前traceId为空或者为默认traceId,则生成新的traceId
if (StringUtils.isBlank(traceId) || TraceIdUtils.defaultTraceId(traceId)){
traceId = TraceIdUtils.genTraceId();
}
//设置traceId
TraceIdUtils.setTraceId(traceId);
}
}
package com.importer.common.util;
import com.importer.common.constant.ImporterConstant;
import org.apache.commons.lang.StringUtils;
import org.slf4j.MDC;
import java.util.UUID;
/**
* Created by zhanghuan on 2019/12/11.
*/
public class TraceIdUtils {
/**
* 当traceId为空时,显示的traceId。随意。
*/
private static final String DEFAULT_TRACE_ID = "0";
/**
* 设置traceId
*/
public static void setTraceId(String traceId) {
//如果参数为空,则设置默认traceId
traceId = StringUtils.isBlank(traceId) ? DEFAULT_TRACE_ID : traceId;
//将traceId放到MDC中
MDC.put(ImporterConstant.TRACE_ID, traceId);
}
/**
* 获取traceId
*/
public static String getTraceId() {
//获取
String traceId = MDC.get(ImporterConstant.TRACE_ID);
//如果traceId为空,则返回默认值
return StringUtils.isBlank(traceId) ? DEFAULT_TRACE_ID : traceId;
}
/**
* 判断traceId为默认值
*/
public static Boolean defaultTraceId(String traceId) {
return DEFAULT_TRACE_ID.equals(traceId);
}
/**
* 生成traceId
*/
public static String genTraceId() {
return UUID.randomUUID().toString();
}
}
2.3.修改log4j2的layout格式
修改日志的layout格式,将MDC中的traceId打印出来:
原始
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
修改后
<pattern>[%X{trace_uuid}] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
2.4.结果

3.问题
异步方法和子线程无法获取uuid
原因在于栈是线程私有,当我们新建线程的时候,新建的线程跟原来的线程互相独立,也就是说logback无法在新建的线程上获取到值。我们可以将uuid 作为参数传递给异步方法或者子线程,或者可以从上游对象获取到
将uid作为参数传递给异步方法和子线程
来源:oschina
链接:https://my.oschina.net/u/2000675/blog/3142350