Java中请求生成唯一追溯TraceId
一:背景
因为是微服务架构,平常日志太多,看日志不太好查,所以想要从一整个链路当中获取一个唯一标识,比较好定位问题,
原理就是从gateway网关将标识传递到下游,下游服务拿到这个标识,响应结束后将traceId反向写入响应体中
二:具体实现
例如我现在有3个微服务,gateway,center-service,注册中心;一个接口完整的请求链路是从gateway路由到center-service中。
1、Gateway
网关处理请求唯一标识
package com.wondertek.gateway.traceId;import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;
import java.util.UUID;@Component
@Slf4j
public class TraceIdResponseFilter implements GlobalFilter, Ordered {private static final String TRACE_ID_HEADER = "X-Trace-Id";@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 在请求前确保traceId存在List<String> existingTraceIds = exchange.getRequest().getHeaders().get(TRACE_ID_HEADER);String traceId = existingTraceIds != null ? existingTraceIds.get(0) : UUID.randomUUID().toString().replaceAll("-","");log.info("网关传递到下游服务的traceId值为:{}", traceId);exchange.getRequest().mutate().header(TRACE_ID_HEADER, traceId).build();return chain.filter(exchange).then(Mono.fromRunnable(() -> {// 在响应后添加traceId到响应头ServerHttpResponse response = exchange.getResponse();response.getHeaders().add(TRACE_ID_HEADER, traceId);}));}@Overridepublic int getOrder() {// 响应后执行return Ordered.LOWEST_PRECEDENCE;}
}
2、center-service
得将此拦截器注册到您的MVC配置中
package com.wondertek.web.traceId;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
@Slf4j
public class TraceIdInterceptor implements HandlerInterceptor {private static final String TRACE_ID_HEADER = "X-Trace-Id";/*** 这个方法在请求处理之前被调用。在这里,它试图从请求头中获取名为 X-Trace-Id 的值,这通常是一个用于追踪请求在不同微服务之间流转的唯一标识符。* 如果这个标识符存在,它会被保存在请求的属性中,这样在后续的处理流程中可以继续访问和使用这个 traceId。* @param request* @param response* @param handler* @return*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 提取请求头中的traceIdString traceId = request.getHeader(TRACE_ID_HEADER);// 如果traceId存在,则将其存储到请求中供后续使用if (traceId != null) {request.setAttribute(TRACE_ID_HEADER, traceId);}return true;}/*** 这个方法在请求处理之后被调用,但在视图渲染之前执行。* 在这里,它从请求属性中获取之前存储的 traceId,然后将这个 traceId 设置在响应头中。* 这样做保证了,如果请求中包含了 traceId,那么这个 traceId 将会随着响应返回给发起请求的客户端。* @param request* @param response* @param handler* @param modelAndView*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {// 将traceId设置到响应头中,确保返回给调用者Object traceId = request.getAttribute(TRACE_ID_HEADER);if (traceId != null) {response.setHeader(TRACE_ID_HEADER, traceId.toString());}}// 其他方法根据需要实现/*** afterCompletion方法是Spring MVC的 HandlerInterceptor 接口中的一个回调函数,它在整个请求处理完毕后执行,也就是在视图渲染完毕且响应已经被发送给客户端之后。* 这个方法会在请求的最后阶段被调用,通常用于清理资源,记录日志,进行异常处理等操作。* 这个方法提供了四个参数:* HttpServletRequest:当前的请求对象。* HttpServletResponse:当前的响应对象。* Object:处理器(通常是一个控制器)。* Exception:执行处理器过程中抛出的异常。如果没有异常抛出则为null。*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {if (ex != null) {// 日志中记录异常信息log.error("请求处理发生错误,url:{}", ex,request.getServletPath());} else {// 日志中记录请求处理成功log.info("请求处理成功,url:{}",request.getServletPath());}// 这里还可以根据需要进行其他操作}}
将此拦截器注册到您的MVC配置中
package com.wondertek.web.traceId;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;@Configuration
public class TraceIdWebConfig implements WebMvcConfigurer {private final TraceIdInterceptor traceIdInterceptor;@Autowiredpublic TraceIdWebConfig(TraceIdInterceptor traceIdInterceptor) {this.traceIdInterceptor = traceIdInterceptor;}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(traceIdInterceptor);}
}
响应请求体中反写traceId
package com.wondertek.web.traceId;import com.wondertek.web.exception.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.bind.annotation.ControllerAdvice;import java.util.UUID;@ControllerAdvice
@Slf4j
public class TraceIdResponseBodyAdvice implements ResponseBodyAdvice<Object> {private static final String TRACE_ID_HEADER = "X-Trace-Id";@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {//如果只想处理返回类型为Result的控制器方法,您可以在supports方法中做出相应的判断:// return returnType.getParameterType().equals(Result.class);// 可以进一步细化条件,只处理Result类型的响应//这个supports方法的实现总是返回true,这意味着无论返回类型或者转换器类型是什么,当前的ResponseBodyAdvice实现都将被用于所有的响应。return true;}/*** 此代码段是ResponseBodyAdvice接口中beforeBodyWrite方法的一个具体实现。此方法允许您在响应体被发送给客户端前对其进行修改。* @param body* @param returnType* @param selectedContentType* @param selectedConverterType* @param request* @param response* @return*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 从请求头中提取traceIdString traceId = request.getHeaders().getFirst(TRACE_ID_HEADER);if (body instanceof Result) {Result<?> result = (Result<?>) body;if (traceId != null) {log.info("下游服务接收到的traceId值为:{}", traceId);result.setTraceId(traceId);} else {// 如果服务从请求头中获取不到traceId,生成一个新的traceId = UUID.randomUUID().toString().replaceAll("-", "");//log.info("下游服务生成的traceId值为:{}", traceId);result.setTraceId(traceId);}}return body;}
}
Result为自己定义的全局返回对象,其中要加入traceId