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

Spring-Boot合集 同时被 3 个专栏收录
13 篇文章 0 订阅
8 篇文章 0 订阅
19 篇文章 0 订阅

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.定义一个简单的接口:

/**
 * <p></P>
 *
 * @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和生成。

/**
 * <p>traceId工具类</P>
 *
 * @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处理。这里处理有些粗暴,可自行细化。

/**
 * <p>traceId过滤器,用于设置traceId</P>
 *
 * @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"/>

<!-- 增加traceId的格式 -->
<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?
  • 4
    点赞
  • 7
    评论
  • 27
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值