You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 06:15:05