SpringBoot项目实现日志链路追踪

此生再无相见时 提交于 2019-12-12 20:23:25

【推荐】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作为参数传递给异步方法和子线程

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!