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

Spring Boot 3调用SseEmitter.complete()后日志报错如何屏蔽?

问题:Spring Boot 3升级后调用SseEmitter.complete()触发不必要的错误日志

升级到Spring Boot 3后,调用emitter.complete()时,客户端的GET请求能正常完成(符合预期),但服务器日志会输出“响应已提交,无法转发到错误页面”的错误信息,需要阻止该错误日志的生成。

控制器代码

@RestController
public class TradeController {

    @GetMapping(path = "/quotes")
    public ResponseEntity<SseEmitter> getQuotes() {
        return ResponseEntity.ok()
                .header("X-Accel-Buffering", "no")
                .body(createSseEmitter());
    }

    private SseEmitter createSseEmitter() {
        SseEmitter emitter = new SseEmitter(70000L);
        emitter.onCompletion(() -> {
            log.info("Quote SSE emitter is completed for request {}", requestId);
        });
        emitter.onError(throwable -> {
            log.warn("Error on SSE emitter, quote request {}", requestId, throwable);
        });
        emitter.onTimeout(() -> {
            log.warn("Timeout on SSE emitter, quote request {}", requestId);
        });

        return emitter;
    }
}

调用emitter.complete()的代码

try {
    emitter.send(jwtService.createJwtToken(message.toClaimsMap(), 300), TEXT_PLAIN);

    // 响应已完成,不再发送消息
    emitter.complete();
    
} catch (IOException e) {
    // emitter.send()的文档明确说明此时不要调用completeWithError;响应已提交
    log.warn("无法向用户发送响应:客户端已关闭连接:{}", response, e);
} catch (IllegalStateException e) {
    // 注意:当emitter已完成时,emitter.send会抛出未捕获的IllegalStateException
    log.error("无法向用户发送响应:emitter已超时:{}", response, e);
}

相关日志

2024-02-22 16:05:17 2024-02-22 16:05:17,429 [INFO ][http-nio-8080-exec-9] LogFilter.logDetails:66 - [] - [method='GET';uri='/quotes';qs='requestToken=...';path info='null'] 耗时13 ms
2024-02-22 16:05:17 2024-02-22 16:05:17,432 [ERROR][http-nio-8080-exec-9] org.springframework.boot.web.servlet.support.ErrorPageFilter.handleCommittedResponse:222 - [] - 无法转发请求[/quotes]到错误页面,因为响应已提交。因此,响应可能会有错误的状态码。如果你的应用运行在WebSphere应用服务器上,可以通过设置com.ibm.ws.webcontainer.invokeFlushAfterService为false来解决该问题
2024-02-22 16:05:17 org.springframework.security.access.AccessDeniedException: 访问被拒绝
2024-02-22 16:05:17     at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:98)
2024-02-22 16:05:17     at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240)
2024-02-22 16:05:17     at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227)
2024-02-22 16:05:17     at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137)

解决方案

1. 修复根源:解决AccessDeniedException

日志中出现的AccessDeniedException是触发后续错误日志的核心原因——虽然SSE响应已经提交完成,但过滤器链仍在执行,Spring Security的授权过滤器抛出了权限异常,导致ErrorPageFilter尝试转发错误页面失败。

  • 检查Spring Security配置,确保/quotes路径被正确授权,例如添加.antMatchers("/quotes").permitAll()或配置对应角色权限。
  • 验证请求中的requestToken是否能正确解析为合法的认证对象,确保用户访问该接口时具备足够权限。

2. 让ErrorPageFilter忽略SSE请求

如果权限异常无法立即修复,可自定义ErrorPageFilter,让它跳过对SSE请求的错误页面转发:

@Configuration
public class ErrorPageFilterConfig {
    @Bean
    public ErrorPageFilter errorPageFilter() {
        return new ErrorPageFilter() {
            @Override
            protected boolean shouldFilter(HttpServletRequest request) {
                // 忽略/quotes路径的SSE请求
                return !"/quotes".equals(request.getRequestURI()) && super.shouldFilter(request);
            }
        };
    }
}

3. 终止SSE完成后的过滤器链

在SSE响应提交后,直接终止后续过滤器的执行,避免权限过滤器抛出异常:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SseCompletionFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 针对/quotes请求,若响应已提交则直接返回,不执行后续过滤器
        if ("/quotes".equals(request.getRequestURI()) && response.isCommitted()) {
            return;
        }
        filterChain.doFilter(request, response);
    }
}

4. 直接屏蔽特定日志

若以上方法都不适用,可通过日志配置屏蔽ErrorPageFilter的这条错误日志:

  • 若使用Logback,在logback.xml中添加:
    <!-- 降低ErrorPageFilter的日志级别至WARN,不再输出ERROR级别的该日志 -->
    <logger name="org.springframework.boot.web.servlet.support.ErrorPageFilter" level="WARN"/>
    
  • 或精准屏蔽特定日志内容:
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator>
                <expression>message.contains("无法转发请求")</expression>
            </evaluator>
            <OnMismatch>NEUTRAL</OnMismatch>
            <OnMatch>DENY</OnMatch>
        </filter>
        <!-- 其他appender配置 -->
    </appender>
    

内容的提问来源于stack exchange,提问作者Justin Rowe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 16:12:04