SpringBoot项目中通过MDC和自定义Filter操作traceId实现日志链路追踪

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
  • 其他实现方式可自行探索。

3.实现步骤

3.1.原始示例

1.定义一个简单的接口:

/**
 * 

* * @author hanchao */
@RestController public class DemoController { private final Logger logger = Logger.getLogger(DemoController.class); @GetMapping("/demo/by-name") public String demo(String name) { logger.info("name:" + name); return name; } }

2.在浏览器调用接口: http://localhost:8080/demo/by-name?name=zhangsan

3.查看相关日志:

 INFO pers.hanchao.trace.controller.DemoController:19 - name:zhangsan 

3.2.TraceId操作工具类

增加TraceId操作的工具类,提供traceId的默认取值、setter、getter和生成。

/**
 * 

traceId工具类

* * @author hanchao */
public class TraceIdUtil { private static final String TRACE_ID = "traceId"; /** * 当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(TRACE_ID, traceId); } /** * 获取traceId */ public static String getTraceId() { //获取 String traceId = MDC.get(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(); } }

3.3.自定义TraceId过滤器

自定义过滤器,对全部请求进行traceId处理。这里处理有些粗暴,可自行细化。

/**
 * 

traceId过滤器,用于设置traceId

* * @author hanchao */
@WebFilter(urlPatterns = "/*", filterName = "traceIdFilter") @Order(1) public class TraceIdFilter 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("traceId"); //如果当前traceId为空或者为默认traceId,则生成新的traceId if (StringUtils.isBlank(traceId) || TraceIdUtil.defaultTraceId(traceId)){ traceId = TraceIdUtil.genTraceId(); } //设置traceId TraceIdUtil.setTraceId(traceId); } }

3.4.启用自定义过滤器

不要忘记在SpringBoot的启动类加上@ServletComponentScan注解,否则自定义的Filter无法生效。

/**
 * 使用嵌入式容器时,可以使用@ServletComponentScan启用@WebServlet,@ WebFilter和@WebListener注释类的自动注册。
 */
@ServletComponentScan(basePackages = "pers.hanchao.trace.filter")
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

3.5.修改log4j2的layout格式

修改日志的layout格式,将MDC中的traceId打印出来:


<PatternLayout pattern="%5p %c:%L - %m %throwable{separator( --> )}%n"/>


<PatternLayout pattern="%5p traceId:%X{traceId} %c:%L - %m %throwable{separator( --> )}%n"/>

3.6.受影响的示例

1.在浏览器多次调用接口: http://localhost:8080/demo/by-name?name=zhangsan

2.查看相关日志:

 INFO traceId:5ee05f9b-432c-401f-ae24-6adaf2f31cf4 pers.hanchao.trace.controller.DemoController:19 - name:zhangsan 
 INFO traceId:b835352f-3a22-462c-965e-c426309ae3b8 pers.hanchao.trace.controller.DemoController:19 - name:zhangsan 
 INFO traceId:942d4f8f-f3c6-4688-a534-3429b6c9e92d pers.hanchao.trace.controller.DemoController:19 - name:zhangsan 

4.更多思考

  • 如果一个业务操作,需要进行多个服务的多个接口相互调用,则如何传递traceId?

你可能感兴趣的:(Log4j,技术方案,Spring-Boot合集)