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";
@Override
public 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);
}));
}
@Override
public 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
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 提取请求头中的traceId
String 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
*/
@Override
public 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。
*/
@Override
public 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;
@Autowired
public TraceIdWebConfig(TraceIdInterceptor traceIdInterceptor) {
this.traceIdInterceptor = traceIdInterceptor;
}
@Override
public 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";
@Override
public 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
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 从请求头中提取traceId
String 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