Spring应用请求日志优化:如何避免Swagger页面冗余日志?
Spring 请求响应日志去重与TraceId持久化解决方案
一、过滤冗余静态资源请求
要解决Swagger页面访问时产生大量静态资源日志的问题,核心是在日志拦截逻辑中排除特定路径。不管用OncePerRequestFilter还是Spring MVC拦截器,都可以通过路径匹配跳过不需要记录的请求:
基于OncePerRequestFilter的实现
重写shouldNotFilter方法,指定需要排除的路径(比如Swagger静态资源、前端静态文件等):
@Component public class RequestLogFilter extends OncePerRequestFilter { // 定义不需要记录日志的路径规则 private static final Set<String> EXCLUDED_PATHS = Set.of( "/swagger-ui/**", "/v3/api-docs/**", "/css/**", "/js/**", "/images/**", "/favicon.ico" ); @Override protected boolean shouldNotFilter(HttpServletRequest request) { String requestUri = request.getRequestURI(); return EXCLUDED_PATHS.stream().anyMatch(uri -> requestUri.startsWith(uri)); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 请求前日志记录逻辑 log.info("Received request: {} {}", request.getMethod(), request.getRequestURI()); chain.doFilter(request, response); // 响应后日志记录逻辑 log.info("Sent response: {} {} | Status: {}", request.getMethod(), request.getRequestURI(), response.getStatus()); } }
基于WebMvcConfigurer拦截器的实现
在拦截器注册时直接指定排除路径:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RequestLogInterceptor()) .excludePathPatterns( "/swagger-ui/**", "/v3/api-docs/**", "/css/**", "/js/**" ); } }
二、TraceId持久化方案
用MDC(Mapped Diagnostic Context) 存储TraceId是Spring生态中最常用的方案,它能保证TraceId在当前请求线程的所有日志中可关联,且天然支持请求生命周期的清理:
1. 在过滤器中注入TraceId
修改之前的RequestLogFilter,加入TraceId生成与MDC绑定逻辑:
@Component public class RequestLogFilter extends OncePerRequestFilter { private static final Logger log = LoggerFactory.getLogger(RequestLogFilter.class); private static final Set<String> EXCLUDED_PATHS = Set.of(/* ... 排除路径 ... */); @Override protected boolean shouldNotFilter(HttpServletRequest request) { // ... 路径判断逻辑 ... } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 优先取请求头中已有的TraceId(比如网关传递的),没有则生成 String traceId = request.getHeader("X-Trace-Id"); if (traceId == null || traceId.isBlank()) { traceId = UUID.randomUUID().toString().replace("-", ""); } // 将TraceId放入MDC,绑定当前线程 MDC.put("traceId", traceId); // 写入响应头,方便前端/下游服务排查 response.setHeader("X-Trace-Id", traceId); try { log.info("Request received: method={}, uri={}, traceId={}", request.getMethod(), request.getRequestURI(), traceId); chain.doFilter(request, response); log.info("Response sent: method={}, uri={}, status={}, traceId={}", request.getMethod(), request.getRequestURI(), response.getStatus(), traceId); } finally { // 必须清理MDC,避免线程池复用导致的上下文污染 MDC.remove("traceId"); } } }
2. 日志框架配置TraceId输出
以Logback为例,在logback.xml的日志格式中加入%X{traceId},即可在所有日志中自动带上TraceId:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg | traceId=%X{traceId}%n</pattern> </encoder> </appender>
3. 异步请求的TraceId传递
如果项目中有@Async异步方法,需要配置TaskDecorator将MDC上下文拷贝到异步线程:
@Configuration @EnableAsync public class AsyncConfig { @Bean public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(20); // 配置MDC上下文传递 executor.setTaskDecorator(runnable -> { Map<String, String> mdcContext = MDC.getCopyOfContextMap(); return () -> { try { if (mdcContext != null) { MDC.setContextMap(mdcContext); } runnable.run(); } finally { MDC.clear(); } }; }); executor.initialize(); return executor; } }
三、更精准的日志触发控制
如果需要进一步缩小日志范围,比如只记录接口请求(而非页面请求),可以在过滤器中增加判断逻辑:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 只记录Accept为JSON的接口请求 String acceptHeader = request.getHeader("Accept"); if (acceptHeader != null && acceptHeader.contains("application/json")) { // 执行日志记录逻辑 String traceId = MDC.get("traceId"); log.info("API Request: {} {} | traceId={}", request.getMethod(), request.getRequestURI(), traceId); } chain.doFilter(request, response); }
内容的提问来源于stack exchange,提问作者Ilja Tarasovs
相关产品推荐
相关产品推荐

