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>
效果如下
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律