小弟这两天在部署springcloud项目,需要在日志中打印出请求相关信息,自定义微服务请求链路追踪,所以写下这篇文章。
首先明确,traceId在springcloud gateway中生成,然后向服务中传递。那么首先配置网关层。
一、springcloud gateway中
1、新建 TraceFilter (在authfilter(鉴权)执行前)
/**
* traceId 过滤器
*/
@Order(-1)
@Slf4j
@Component
public class TraceFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String traceId = UUID.randomUUID().toString(true);
//对请求对象request进行增强
ServerHttpRequest req = request.mutate().headers(httpHeaders -> {
//httpHeaders 封装了所有的请求头
httpHeaders.add(Constant.REQUEST_ID, traceId);
MDC.put(Constant.REQUEST_ID, traceId);
}).build();
log.info("request ------ request-id:[{}] uri:[{}]", traceId, req.getURI().getPath());
exchange.mutate().request(req);
return chain.filter(exchange);
}
}
3、OpenFeign添加traceId全链路监控
因为authFilter通过openfeign远程调用鉴权服务,实现鉴权功能,所以也要将traceId通过openfeign传递到鉴权服务(放置在请求头上)
/**
* 配置OpenFeign透传traceId
*/
@Component
public class OpenFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String traceId = MDC.get(Constant.REQUEST_ID);
requestTemplate.header(Constant.REQUEST_ID, traceId);
}
}
因为通常更需要关注的是下游的多个服务间日志串联,所以gateway可写可不写入MDC,需要把生成的traceid传到下游就可以了,
二、springcloud服务
1、新建过滤器 TraceFilter
/**
* 拦截traceId,并设置到MDC中
*/
@Order(0)
@Component
@Slf4j
public class TraceFilter implements Filter {
/**
* 目标方法执行前
* 该方法在控制器处理请求方法前执行
*
* @param servletRequest 请求
* @param servletResponse 响应
* @param filterChain 过滤链
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String requestId = ServletRequestUtils.getRequestId();
MDC.put(Constant.REQUEST_ID, requestId);
MDC.put(Constant.ACCOUNT, ServletRequestUtils.getCurrentLoginUserAccount());
MDC.put(Constant.REQUEST_HEADER_CURRENT_USER, ServletRequestUtils.getCurrentUser());
try {
HttpServletRequest servletRequest1 = (HttpServletRequest) servletRequest;
log.info("request start ------ request-id:[{}] account:[{}] uri:[{}]", requestId, ServletRequestUtils.getCurrentLoginUserAccount(),servletRequest1.getRequestURI());
filterChain.doFilter(servletRequest, servletResponse);
log.info("request finished ------ request-id:[{}] account:[{}] uri:[{}]", requestId, ServletRequestUtils.getCurrentLoginUserAccount(), servletRequest1.getRequestURI());
} finally {
MDC.clear();
}
}
}
注意:这里使用filter的原因是在方法调用前后可以加上自己想要的日志去标识,并且在方法执行完后手动清除MDC。
2、如果调用方法中,有通过openfeign调用其他服务,所以也要将traceId通过openfeign传递到其他服务(放置在请求头上)
/**
* 配置OpenFeign透传traceId和当前用户
*/
@Component
public class OpenFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String traceId = MDC.get(Constant.REQUEST_ID);
String currentUser = MDC.get(Constant.REQUEST_HEADER_CURRENT_USER);
//traceId
requestTemplate.header(Constant.REQUEST_ID, traceId);
//当前用户信息
requestTemplate.header(Constant.REQUEST_HEADER_CURRENT_USER, currentUser);
}
}
三、日志
小弟使用的默认的logback.xml
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{request-id}] [%X{account}] [%-5level] %logger{50} - %msg%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
通过%X{request-id} 可将traceId打印在日志中,%X{account}将当前用户打印在日志中。
小弟发送一次请求,结果如下图所示:
网关服务:
2024-03-12 10:27:38.700 [reactor-http-nio-2] [25c5fbb1d28e401d8575a75ec54ae367] [INFO ] c.s.popper.huntergateway.filter.TraceFilter - request ------ request-id:[25c5fbb1d28e401d8575a75ec54ae367] uri:[/assetsSift/queryAssetsSiftStrategy]
鉴权服务:
2024-03-12 10:01:03.511 [http-nio-9002-exec-3] [25c5fbb1d28e401d8575a75ec54ae367] [hunterAdmin] [INFO ] c.s.popper.huntercommonconfig.config.TraceFilter - request start ------ request-id:[25c5fbb1d28e401d8575a75ec54ae367] account:[hunterAdmin] uri:[/auth/checkToken]
2024-03-12 10:01:03.520 [http-nio-9002-exec-3] [25c5fbb1d28e401d8575a75ec54ae367] [hunterAdmin] [INFO ] c.s.popper.hunteruser.controller.AuthController - 校验token
2024-03-12 10:01:03.550 [http-nio-9002-exec-3] [25c5fbb1d28e401d8575a75ec54ae367] [hunterAdmin] [INFO ] c.s.popper.huntercommonconfig.config.TraceFilter - request finished ------ request-id:[25c5fbb1d28e401d8575a75ec54ae367] account:[hunterAdmin] uri:[/auth/checkToken]
业务服务:
2024-03-12 10:01:03.642 [http-nio-9003-exec-1] [25c5fbb1d28e401d8575a75ec54ae367] [lei] [INFO ] c.s.popper.huntercommonconfig.config.TraceFilter - request start ------ request-id:[25c5fbb1d28e401d8575a75ec54ae367] account:[lei] uri:[/assetsSift/queryAssetsSiftStrategy]
2024-03-12 10:01:03.680 [http-nio-9003-exec-1] [25c5fbb1d28e401d8575a75ec54ae367] [lei] [INFO ] c.s.p.h.controller.AssetsSiftStrategyController - queryAssetsSiftStrategy param:{}
2024-03-12 10:01:03.812 [http-nio-9003-exec-1] [25c5fbb1d28e401d8575a75ec54ae367] [lei] [INFO ] c.s.popper.huntercommonconfig.config.TraceFilter - request finished ------ request-id:[25c5fbb1d28e401d8575a75ec54ae367] account:[lei] uri:[/assetsSift/queryAssetsSiftStrategy]
备注:
- 各微服务均需要添加logbak.xml文件实现打印traceId。
- 各微服务间通过openfeign在header上添加traceId来进行传递。
- 各微服务均须添加traceFilter,来实现接收traceId并打印在日志中。
综上就可以实现通过一个traceId查询到全链路的日志了。
希望自己遇到的诸多困难,能带给各位一点点小小的帮助,感谢。