在上文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://www.bmabk.com/index.php/post/12286.html