spring cloud sleuth入门使用
一 背景二 简介三 入门案例3.1provider3.1.1 创建springboot项目添加sleuth依赖3.1.2 配置yml定义项目名称3.1.3 创建提供者controller并提供feign接口
3.2consumer3.1.1 创建ConsumerSpringboot项目添加sleuth依赖3.1.2 配置yml定义项目名称3.1.3 创建消费者controller并调用feign接口
3.3启动服务测试3.4添加日志输出
四 Result返回体中自动添加traceId4.1添加拦截器,设置traceId到MDC中4.2 Result返回体中添加字段4.3 全局异常处理4.4模拟异常观察结果
五 案例源码传送带
一 背景
实际项目中,我们会涉及到多个服务间的调用,想必大家在实际日志分析接口调用链路时会遇到这样的情况: 通过A服务接口能够清晰的看见调用情况,要是想去B服务排查一下A服务调用的这次请求中的哪个接口时,单次调用根据接口名直接能够查询到,但是在并发情况下,我们就很难辨别出来接口是由哪次请求调用的。 所以我们就想有没有一种框架工具能够自动帮我记录整个调用链路情况,进行链路追踪。spring cloud sleuth就解决了这些痛点问题
二 简介
详细介绍 Spring Cloud 系列之 Sleuth 链路追踪(一) Spring Cloud Sleuth 为 Spring Cloud 实现了分布式跟踪解决方案。兼容 Zipkin,HTrace 和其他基于日志的追踪系统,例如 ELK(Elasticsearch 、Logstash、 Kibana)。
Spring Cloud Sleuth 提供了以下功能:
链路追踪:通过 Sleuth 可以很清楚的看出一个请求都经过了那些服务,可以很方便的理清服务间的调用关系等。性能分析:通过 Sleuth 可以很方便的看出每个采样请求的耗时,分析哪些服务调用比较耗时,当服务调用的耗时随着请求量的增大而增大时, 可以对服务的扩容提供一定的提醒。数据分析,优化链路:对于频繁调用一个服务,或并行调用等,可以针对业务做一些优化措施。可视化错误:对于程序未捕获的异常,可以配合 Zipkin 查看。
三 入门案例
SpringBoot 2.4.2、Spring Cloud 2020.0.6
案例中创建一个提供者、消费者。在消费者中以fegin接口的形式调用提供者接口。通过链路调用我们来观察traceid和spanId情况。
3.1provider
3.1.1 创建springboot项目添加sleuth依赖
3.1.2 配置yml定义项目名称
server:
port: 8088
spring:
application:
name: provider
3.1.3 创建提供者controller并提供feign接口
@Slf4j
@RestController
@RequestMapping("/CommentRest")
public class CommentRestController {
/**
* 数值返回值测试,是否能正常封装返回体
*/
@GetMapping("getId")
public Integer getId() {
log.info("provider:{}", "getId");
return 1;
}
}
/**
* @ClassName: CommentRestApi
* @Description: CommentRestApi
* @Author: wang xiao le
* @Date: 2023/08/25 23:32
**/
@FeignClient(url = "http://localhost:8088", path = "CommentRest", value = "provider")
public interface CommentRestApi {
/**
* 数值返回值测试,是否能正常封装返回体
*/
@GetMapping("getId")
Integer getId();
}
3.2consumer
3.1.1 创建ConsumerSpringboot项目添加sleuth依赖
3.1.2 配置yml定义项目名称
server:
port: 8089
spring:
application:
name: consumer
3.1.3 创建消费者controller并调用feign接口
/**
* @ClassName: TestController
* @Description: TestController
* @Author: wang xiao le
* @Date: 2023/08/26 15:51
**/
@Slf4j
@RestController
@RequestMapping
public class TestController {
@Resource
CommentRestApi commentRestApi;
@GetMapping("getId")
public Integer getId() {
log.info("SubjectCategoryController.add.dto:{}", "JSON.toJSONString(subjectCategoryBO)");
System.out.println("true = " + true);
return commentRestApi.getId();
}
}
3.3启动服务测试
调用消费者接口 http://localhost:8089/getId 通过结果发现能够输出应用名,traceId,spanId。 提供者中继承了父中traceId,并生成新的spanid。
3.4添加日志输出
上述测试我们只能够在控制台中看见raceId,spanId。由于日志文件中并没有配置输出raceId,spanId。所以看不见。在logback中通过
%X{traceId} %X{spanId}
可以获取对应的值
再次测试
四 Result返回体中自动添加traceId
实际开发过程中,调用方想让我们定位到它这一次的调用过程,平时我们是不是先打开服务日志,通过时间点去查询这一次请求情况!!!有没有想过,要是能有一个唯一ID作为此次调标识,我们根据这个唯一id去搜索那不就很快能够定位到?上面案例中已经给出一次调用唯一ID,我们现在直接通过调用方给我们唯一ID去搜索,所我们需要将traceId一并返回。 尤其在异常情况下,直接通过traceId去定位问题更加方便。
4.1添加拦截器,设置traceId到MDC中
将每一次请求的traceId设置到MDC中,后续使用可以从MDC中取。还可以在拦截器中将traceId设置到相应头中。 注意在afterCompletion要进行MDC删除.
package com.wxlalt.controller;
import org.springframework.lang.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.TraceContext;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: ZdxlzBusinessTraceAdapter
* @Description:
* @Author wxl
* @Date 2024-03-08
* @Version 1.0.0
**/
@Component
@Slf4j
public class ZdxlzBusinessTraceAdapter implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Tracer tracer = Tracing.currentTracer();
TraceContext context = tracer.currentSpan().context();
String traceId = context.traceIdString();
String spanId = context.spanIdString();
String trace = MDC.get(MdcConstant.MDC_TRACE);
log.info("MDC_TRACE:{} SPAN_ID:{}",trace,spanId);
MDC.put(MdcConstant.TRACE_ID, traceId);
MDC.put(MdcConstant.SPAN_ID, spanId);
//将traceId添加到请求头中
response.setHeader(MdcConstant.TRACE_ID, MDC.get(MdcConstant.TRACE_ID));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
MDC.clear();
}
}
注册拦截,使拦截器生效
package com.wxlalt.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @ClassName: WebMvcConfiguration
* @Description: Spring Boot 2.0 解决跨域问题
* @Author wxl
* @Date 2024-03-08
* @Version 1.0.0
**/
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private ZdxlzBusinessTraceAdapter traceAdapter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(traceAdapter);
}
}
4.2 Result返回体中添加字段
在Result中添加requestId字段,自动获取MDC中存储的traceId并赋值。这样就每次使用Result时就能够自动返回traceId了。
package com.wxlalt.controller;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import java.io.Serializable;
/**
* @Author: jiee
* @Date: 2020/11/5 9:31
*/
@Data
@Slf4j
public class Result
//返回标记,成功为0,失败非0
private String code;
//返回消息
private String msg;
//返回中的数据
private T data;
@JsonProperty("request_id")
private String requestId;
/**
* 可以传入封装的数据,和封装的信息,失败或者成功
*
* @param data
* @param msg
*/
public Result(T data, String msg) {
this.code = "200";
this.msg = msg;
this.data = data;
this.requestId = MDC.get(MdcConstant.TRACE_ID);
}
/**
* 含参的构造方法
*
* @param code
* @param msg
* @param data
*/
public Result(String code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
this.requestId = MDC.get(MdcConstant.TRACE_ID);
}
/**
* @Author jlwang
* @Date 2021/1/19 13:52
* @Description 含参构造方法
*/
public static Result error(String[] error) {
return new Result(error[0], error[1], null);
}
/**
* @Author SongYang
*/
public static Result error(String[] error, String msg) {
return new Result(error[0], error[1]+","+msg, null);
}
/**
* 没有object对象的转化
*
* @param json
* @return
*/
public static Result format(String json) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(json, Result.class);
} catch (Exception e) {
log.error("json解析异常,json:{}",json,e);
}
return null;
}
}
4.3 全局异常处理
项目中抛出的异常我们一般会做统一处理,在异常时返回信息需要封装成Result对象返回。这样我们系统出现异常,调用方也能够接收到traceId,方便我们定位问题点。
package com.wxlalt.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 统一拦截异常
* @author xlwang
*/
@RestControllerAdvice
@Slf4j
public class ControllerExceptionHandler {
/**
* 顶级异常捕获并统一处理,当其他异常无法处理时候选择使用
*/
@ExceptionHandler({Exception.class})
public Result> handle(Exception ex) {
log.error(ex.getMessage(), ex);
return new Result<>("500","系统异常",null);
}
}
4.4模拟异常观察结果
public class TestController {
@Resource
CommentRestApi commentRestApi;
@GetMapping("getId")
public Result
log.info("SubjectCategoryController.add.dto:{}", "JSON.toJSONString(subjectCategoryBO)");
System.out.println("true = " + true);
int i = 1 / 0;
return new Result<>(commentRestApi.getId(), "成功");
}
}
通过接口调用返回异常提示语,并且能够返回traceId,我们可以拿着traceId愉快的去查找错误日志啦!!!
五 案例源码传送带
案例源码传送带
Sleut优秀博客
0.Spring Cloud Sleuth 全链路日志跟踪解决方案(强烈推荐) 1.springCloud整合zipkin 2.【Docker】安装 Zipkin
相关阅读
发表评论