在上文ELK日志集成中讲到了日志的排查方法,现在聊聊使用spring-cloud-sleuth和MDC+拦截器两种方法操作traceId实现全链路调用日志跟踪

方法一:直接使用spring-cloud-starter-sleuth进行日志链路跟踪

 在pom.xml文件中直接配置依赖

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-sleuth</artifactId>
	</dependency>

	<dependency>
		<groupId>net.logstash.logback</groupId>
		<artifactId>logstash-logback-encoder</artifactId>
		<version>5.2</version>
	</dependency>
</dependencies>

<dependencyManagement>
	<dependencies>
	    <dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-dependencies</artifactId>
        <!--我使用的springboot的版本是2.1.6的,对应的springcloud的版本参考spring官网-->
		<version>Greenwich.SR4</version>
		<type>pom</type>
		<scope>import</scope>
	    </dependency>
	</dependencies>
</dependencyManagement>

然后在logback-spring.xml中配置

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} traceId[%X{X-B3-TraceId}] spanId[%X{X-B3-SpanId}] parentSpanId[%X{X-B3-ParentSpanId}] [%thread] %-5level %logger{36} -%msg%n</pattern>

在配置文件中配置sleuth的相关配置

启动应用,发送一笔交易 

但是这样有一个问题,虽然启动没有问题,业务一切正常,我在查看日志文件的时候 ,发现多了一个应用的日志文件目录,文件名是xxx_IS_UNDEFINED,若是logback-spring.xml中设置了spring.cloud.client.ip-address和spring.application.name默认值,则会多出一个默认值的日志文件目录,这是因为此时是一个springcloud项目,springcloud项目logback-spring.xml配置优先于application.yml配置文件加载,解决方法:创建bootstrap.yml文件,把spring.application.name放在bootstrap.yml文件中

 在此推荐一个别人写的traceId链路跟踪文章:SpringBoot之微服务日志链路追踪
方便的是此项目可以在阿里云的镜像仓库下载,方便直接引入,其项目github地址
https://github.com/purgeteam/log-trace-spring-boot/tree/master/log-trace-spring-boot-starter

 方法二:MDC+过滤器操作traceId

 此方法无需映入上述的spring-cloud-start-sleuth依赖;当然除了过滤器,拦截器也可以实现,大致实现步骤差不多,本文就使用拦截器来实现

首先定义一下常量值

/**
 * @Classname MyConstant
 * @Description 常量值的类
 * @Date 2021/8/16 10:05
 * @Created by gangye
 */
public class MyConstant {
    /**
     * 日志跟踪标识
     */
    public static final String TRACE_ID_MDC_FIELD = "traceId";
    public static final String SPAN_ID_MDC_FIELD = "spanId";
    public static final String PARENT_SPAN_ID_MDC_FIELD = "parentSpanId";
    public static final String TRACE_ID_HTTP_FIELD = "X-B3-TraceId";
    public static final String SPAN_ID_HTTP_FIELD = "X-B3-SpanId";
    public static final String PARENT_SPAN_ID_HTTP_FIELD = "X-B3-ParentSpanId";
    public static final String SAMPLED_HTTP_FIELD = "X-B3-Sampled";
}

编写一个过滤器,此处我继承了spring的OncePerRequestFilter,如果实现apache中Servlet的Filter也行,在doFilter中做MDC处理,在最终destroy中要将MDC塞入的清除或移除。

import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

/**
 * @Classname TraceIdFilter
 * @Description TraceId使用的过滤器
 * @Date 2021/8/16 10:07
 * @Created by gangye
 */
//@WebFilter(urlPatterns = "/*",filterName = "traceIdFilter")   //过滤器过滤路径可以在注解中配置,也可在后面的配置类中在代码中增加
public class TraceIdFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String traceId = request.getHeader(MyConstant.TRACE_ID_HTTP_FIELD);
        String parentSpanId = request.getHeader(MyConstant.PARENT_SPAN_ID_HTTP_FIELD);
        if (StringUtils.isNotBlank(parentSpanId)){
            MDC.put(MyConstant.PARENT_SPAN_ID_MDC_FIELD,parentSpanId);
        }
        String spanId = request.getHeader(MyConstant.SPAN_ID_HTTP_FIELD);
        if (StringUtils.isBlank(traceId)){
            traceId = UUID.randomUUID().toString().replace("-","").substring(0,16);
            spanId = traceId;
        }
        if (StringUtils.isBlank(spanId)){
            spanId = UUID.randomUUID().toString().replace("-","").substring(0,16);
        }
        MDC.put(MyConstant.TRACE_ID_MDC_FIELD, traceId);
        MDC.put(MyConstant.SPAN_ID_MDC_FIELD, spanId);

        try {
            filterChain.doFilter(request,response);
        }finally {
            MDC.remove(MyConstant.TRACE_ID_MDC_FIELD);
            MDC.remove(MyConstant.SPAN_ID_MDC_FIELD);
            MDC.remove(MyConstant.PARENT_SPAN_ID_MDC_FIELD);
        }
    }
}

编写配置类

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.servlet.ConditionalOnMissingFilterBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

/**
 * @Classname TraceSleuthConfiguration
 * @Description 过滤器配置类
 * @Date 2021/8/16 13:31
 * @Created by gangye
 */
@Slf4j
@ConditionalOnClass({TraceIdFilter.class})
@ConditionalOnWebApplication
@AutoConfigureAfter(name = {"org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration"})
public class TraceSleuthConfiguration {
    @Bean(
        name = {"traceIdMDCFilterBean"}
    )
    @ConditionalOnMissingBean(
        type = {"org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration"}
    )
    @ConditionalOnMissingFilterBean({TraceIdFilter.class})
    public FilterRegistrationBean<TraceIdFilter> traceIdFilterBean(){
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new TraceIdFilter());
        registration.addUrlPatterns(new String[]{"/*"});
        registration.setOrder(2);//如果有多个过滤器的,设置优先顺序,值越小越先
        registration.setName("traceIdFilter");
        log.info("未检测到[Spring-Sleuth]组件,启用自定义链路追踪器");
        return registration;
    }
}

 在resources目录下创建META-INF文件夹,创建spring.factories文件,编写如下内容,value的值就写过滤器的路径

在日志打印配置中配置traceId和spanId

启动项目,做一笔请求在日志中可以清除的看到,在没有引入spring-cloud-sleuth依赖的情形下,spring使用了我们自定义的链路跟踪

 相关代码我已上传gitee

https://gitee.com/gangye/springboot_mutiDemos/releases/v1.0

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐