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
相关产品推荐
相关产品推荐

