使用WebClient检测未启动服务器时无法屏蔽WARN日志的问题
解决方案
这个问题我之前处理过,核心原因是Reactor Netty的HttpClientConnect组件会在TCP连接失败/提前关闭时自动输出WARN日志,而我们之前的异常回调只覆盖了请求/响应阶段,没触达TCP连接层面的异常处理。下面是几个优雅的解决思路:
1. 从HttpClient层面拦截TCP连接异常
我们可以在tcpConfiguration里添加TCP连接阶段的异常处理,同时通过onErrorResume消化这类异常,避免内部日志触发:
HttpClient httpClient = HttpClient.create(ConnectionProvider.newConnection()) .tcpConfiguration(tcpClient -> tcpClient .option(CONNECT_TIMEOUT_MILLIS, httpConnectionTimeoutMillis) .doOnConnected(conn -> conn .addHandlerLast(new ReadTimeoutHandler(httpReadTimeoutMillis, MILLISECONDS))) // 拦截TCP连接失败的异常 .doOnError(ConnectException.class, e -> { logger.debug("Server not listening yet, TCP connection failed: {}", e.getMessage()); }) // 拦截连接提前关闭的异常 .doOnError(PrematureCloseException.class, e -> { logger.debug("Connection closed prematurely before server response: {}", e.getMessage()); })) // 优化请求错误日志,只在非连接类异常时输出WARN .doOnRequestError((request, e) -> { if (!(e instanceof PrematureCloseException || e.getCause() instanceof ConnectException)) { logger.warn("onRequestError: We have a request error: {}.", e.getMessage()); } }) .doOnResponseError((response, e) -> { logger.warn("onResponseError: We have a response error: {}.", e.getMessage()); }) // 关键:用onErrorResume消化连接类异常,不让它们传递到内部日志处理器 .onErrorResume(e -> { if (e instanceof ConnectException || e instanceof PrematureCloseException) { return Mono.empty(); } return Mono.error(e); });
2. 在WebClient请求链路中提前捕获异常
在发起请求时,通过onErrorResume精准捕获WebClient封装的连接异常,直接返回你需要的默认值,同时阻止异常向上传递:
webClient.get() .uri("/actuator/health") .retrieve() .bodyToMono(String.class) .onErrorResume(throwable -> { // 识别WebClient封装的连接类异常 if (throwable instanceof WebClientRequestException) { Throwable cause = throwable.getCause(); if (cause instanceof ConnectException || cause instanceof PrematureCloseException) { logger.debug("Server still starting, health check failed: {}", throwable.getMessage()); // 返回你需要的默认状态,比如"DOWN" return Mono.just("DOWN"); } } // 其他异常正常抛出 return Mono.error(throwable); });
3. 全局拦截Reactor的异常丢弃事件
如果上面两种方式还不够,你可以通过Reactor的Hooks注册全局异常处理器,专门拦截PrematureCloseException这类不需要输出WARN的异常:
// 全局拦截异常丢弃事件,避免内部日志输出 Hooks.onErrorDropped(throwable -> { if (throwable instanceof PrematureCloseException || (throwable instanceof ConnectException && throwable.getMessage().contains("Connection refused"))) { // 用DEBUG级别记录,或者直接忽略 logger.debug("Ignoring expected connection failure during server startup: {}", throwable.getMessage()); } else { // 其他异常按照默认逻辑处理 Hooks.onErrorDropped(throwable); } });
注意:全局钩子要谨慎使用,最好只在测试场景或者特定初始化流程中启用,避免影响其他正常请求的异常处理。
为什么之前的代码没生效?
你之前配置的doOnRequestError和doOnResponseError是在HTTP请求/响应阶段触发的回调,但HttpClientConnect的WARN日志是在TCP连接建立阶段产生的,这个阶段的异常没有被你的回调覆盖,所以需要在TCP层面或者全局异常处理中拦截。
内容的提问来源于stack exchange,提问作者Bert Laverman
相关产品推荐
相关产品推荐

