微服务-网关Spring Gateway进阶-日志跟踪唯一ID

6,103 阅读3分钟

写在前面的话

该篇文章采用SpringGateway实现traceId存在缺陷,思路可以参考,但是实现是有问题的,这个问题我通过SpringBoot自己实现了网关的功能,在下一篇文章中有说明【微服务-网关Spring Gateway进阶-网关参数设计与封装】,同时最新的源码中上传了基于SpringBoot实现网关的功能,可以参考。

一、概述

在微服务中,服务链路调用较多,如果日志没有明显的标识,很难排查关联的日志信息,因此在日志文件中,对于同一个请求,需要有同一个日志追踪ID(常称为traceId)。这里有两个层次的要求,第一个是同一个微服务中同一个请求的日志包含相同的traceId;第二个不同微服务之间的同一个请求,traceId也需要保持一致。

二、MDC简介

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。我采用的是log4j2,也支持这种功能。微服务也是一个多线程的应用,在MDC中添加每个线程(请求)的特殊参数,即traceId,能够区分不同线程的日志信息,有利于排查问题。

三、网关实现

在SpringGateWay中通过添加一个全局的TraceInfoFilter,通过MDC轻松实现。注意这里要将该Filter的Order设置低一些,这样他的优先级就比较高,其他的业务逻辑处理都必须在他之后,才能用到这里设置的traceId属性。

/**
 * 生成日志唯一ID,并放到请求头信息中,以及集成slf4j
 */
@Component
public class TraceInfoFilter implements GlobalFilter, Ordered {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        logger.info("====== TraceInfoFilter start ======");
        String traceId = TraceIdUtil.shortUUID();
        // 1.将traceId传递给微服务
        ServerHttpRequest request = exchange.getRequest().mutate().header("traceId", traceId).build();
        // 2.将traceId设置到slf4j中,日志打印模板配置打印traceId
        MDC.put("traceId", traceId);
        return chain.filter(exchange.mutate().request(request).build());
    }

    @Override
    public int getOrder() {
        return WRITE_RESPONSE_FILTER_ORDER - 2;
    }
}

第二步要调整log4j2.xml的输入模板配置,这里需要添加traceId属性,我这个例子只修改了控制台输出。注意traceId是自定义属性,其写法为%X{traceId}

<configuration status="INFO" monitorInterval="30">
    <properties>

        <Property name="log_path">${sys:user.home}/logs</Property>
    </properties>
    <!--先定义所有的appender -->
    <appenders>
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式 -->
            <PatternLayout charset="UTF-8" pattern="[%d][%-5p][%t][%c:%L][%X{traceId}] %m%n"/>
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
    <loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息 -->
        <logger name="org.springframework" level="INFO"></logger>
        <!--<root level="all">-->
        <root level="ALL">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

测试如下,绿色部分就是增加的traceId打印

image.png

四、微服务实现

这里的思路是不改变原有的请求参数,传递给微服务的链路参数放在header里面。

// 1.将traceId传递给微服务
ServerHttpRequest request = exchange.getRequest().mutate().header("traceId", traceId).build();
// 2.将traceId设置到slf4j中,日志打印模板配置打印traceId
MDC.put("traceId", traceId);
return chain.filter(exchange.mutate().request(request).build());

微服务也需要进行相应的改造,需要添加一个RequestInterceptor,用于处理traceId

/**
 * 请求拦截器
 */
public class RequestInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String traceId = request.getHeader("traceId");
        MDC.put("traceId", traceId);
        return super.preHandle(request, response, handler);
    }

}

在原有的业务代码中打印日志,调试后,就可以发现,gateway中的traceId传递到微服务中,并且对业务代码没有侵入。

image.png

五、总结

  • 工程启动的日志没有traceId,因为如上实现方式是请求类的信息才有添加traceId
  • traceId是UUID,可以优化下加上时间戳、系统信息等,提升可读性
  • 中间出现一个小插曲,在网关设置属性错误导致微服务无法获取traceId

六、源码

gitee.com/animal-fox_…