SpringBoot集成Splunk日志UUID仅主线程存在,业务线程为空问题排查
问题分析与解决方案
核心原因
ThreadContext是基于**线程本地存储(ThreadLocal)**实现的日志上下文容器,数据完全绑定在当前线程中。你在DemoApplication的main方法(主线程)存入的UUID,只能在主线程的日志中生效。
Spring Boot默认使用Tomcat作为Web容器,请求处理的业务线程(比如http-nio-8080-exec-2)来自Tomcat的独立线程池,这些线程和主线程没有上下文共享关系,主线程的ThreadContext数据不会自动传递到这些业务线程里,所以业务线程日志中的uuid字段为空。
解决办法
方法1:通过Spring MVC拦截器注入UUID
创建一个拦截器,在每个请求入口生成UUID并写入ThreadContext,请求结束后清理数据(避免线程复用导致的脏数据):
import org.apache.logging.log4j.ThreadContext; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; public class LogUuidInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 生成请求唯一UUID String requestUuid = UUID.randomUUID().toString(); ThreadContext.put("uuid", requestUuid); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求结束后清理上下文,防止线程池复用残留数据 ThreadContext.remove("uuid"); } }
然后注册这个拦截器到Spring MVC:
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 WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 对所有请求生效 registry.addInterceptor(new LogUuidInterceptor()).addPathPatterns("/**"); } }
方法2:用Servlet Filter实现(更底层)
如果项目不需要Spring MVC拦截器的特性,也可以用Servlet Filter实现同样的逻辑:
import org.apache.logging.log4j.ThreadContext; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; import java.util.UUID; @WebFilter(urlPatterns = "/*") public class LogUuidFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { String requestUuid = UUID.randomUUID().toString(); ThreadContext.put("uuid", requestUuid); chain.doFilter(request, response); } finally { // 确保无论请求成功与否都清理上下文 ThreadContext.remove("uuid"); } } }
记得在启动类上添加@ServletComponentScan注解,让Spring自动扫描并注册这个Filter。
异步请求额外处理
如果项目中有异步方法(比如用@Async注解),还需要配置线程池的上下文传递,否则异步线程的日志依然会丢失UUID:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.apache.logging.log4j.ThreadContext; @Configuration public class AsyncTaskConfig { @Bean public TaskExecutor asyncTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(20); executor.setThreadNamePrefix("async-worker-"); // 配置上下文传递,把主线程的ThreadContext复制到异步线程 executor.setTaskDecorator(runnable -> { // 保存当前线程的日志上下文 var threadContext = ThreadContext.getContext(); return () -> { try { // 恢复上下文到异步线程 ThreadContext.putAll(threadContext); runnable.run(); } finally { // 清理异步线程的上下文 ThreadContext.clearAll(); } }; }); executor.initialize(); return executor; } }
内容的提问来源于stack exchange,提问作者vivek kumar
相关产品推荐
相关产品推荐

