springboot的链路日志

  1.背景

  在开发银行项目的时候有一个生成项目链路日志的需求。所谓的链路日志就是一个请求会经过多个项目的接口调用,它把这个请求内调用到的所有请求通过全局id串起来,通过全局id可以把所有涉及到的系统日志都快速的定位出来,方便线上出现问题时去排查问题。

  2.实现

  查阅了一些资料后发现大致有两种方案去实现。第一种就是使用MDC( log4j 和 logback 提供的一种方便在线多线程条件下记录日志的功能,可以看成是一个与当前线程绑定的 ThreadLocal)。大致的逻辑就是在项目中声明一个过滤器,然后在dofilter方法里面使用MDC去塞一个requestId,requestId可以是从网关那边的header中传递过来,也可以是自己生成的一个uuid,然后在logback-spring.xml中配置相对应的key值。示例如下

@WebFilter(urlPatterns = {"/*"},filterName = "requestFilter")
@Slf4j
public class RequestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.debug("filter init");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        MDC.put("REQUEST_ID", this.getRequestId());
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
        RequestIdUtils.removeRequestId();
        MDC.clear();
    }
    /**
     * 获取RequestId
     * 优先从header头获取,如果没有则自己生成
     *
     * @return RequestId
     */
    private String getRequestId() {
        // 因为如果有网关,则一般会从网关传递过来,所以优先从header头获取
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null && StringUtils.hasText(attributes.getRequest().getHeader("x-request-id"))) {
            HttpServletRequest request = attributes.getRequest();
            String requestId = request.getHeader("x-request-id");
            UUID uuid = UUID.fromString(requestId);
            RequestIdUtils.generateRequestId(uuid);
            return requestId;
        }
        UUID existUUID = RequestIdUtils.getRequestId();
        if (existUUID != null) {
            return existUUID.toString();
        }
        RequestIdUtils.generateRequestId();
        return RequestIdUtils.getRequestId().toString();
    }
}
public class RequestIdUtils {
    private static final ThreadLocal<UUID> requestIdHolder = new ThreadLocal<>();

    private RequestIdUtils() {

    }

    public static void generateRequestId() {
        requestIdHolder.set(UUID.randomUUID());
    }

    public static void generateRequestId(UUID uuid) {
        requestIdHolder.set(uuid);
    }

    public static UUID getRequestId() {
        return requestIdHolder.get();
    }

    public static void removeRequestId() {
        requestIdHolder.remove();
    }
}

因为我在filter里面put的key是REQUEST_ID,故logback-spring.xml里的核心配置如下:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                %d{yyyy-MM-dd HH:mm:ss} [%X{REQUEST_ID}]-[%thread] %-5level %logger - %msg%n
            </Pattern>
        </layout>
    </appender>

最终的效果如下:

 

后面觉得第一种太繁琐了就使用了第二种方案,第二种就是使用sleuth依赖去实现,引入依赖之后在logback-spring.xml中配置下tarceId跟spanId就可以了。这里的spanId是基本工作单元,发送一个远程调度任务 就会产生一个Span,Span是一个64位ID唯一标识的。tarceId就是一系列Span组成的一个树状结构。tarceId 也用一个 64 位的 id 唯一标识,trace 中的所有 span 都共享该 tarceId

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

这里有一个点要注意下,当时排查了很久。traceId跟spanId是sleuth的3.X以上版本配置;3.X以下版本要配置X-B3-TraceId跟X-B3-SpanId

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId:-},%X{spanId:-}] [%thread] %-5level %logger - %msg%n
            </Pattern>
        </layout>
 </appender>

效果如下

 

posted @ 2024-05-11 16:52  写字楼间写字员  阅读(14)  评论(0编辑  收藏  举报