如何根据api重试次数 来动态改变日志级别
背景:
在开发中,根据请求的尝试次数(重试次数)来动态改变日志级别,是一个非常专业且优雅的重设。这能有效防止日志刷屏(避免偶尔的网络抖动触发大量的 ERROR 报警)。
要实现这个功能,通常不能靠纯粹的日志配置文件(如 logback.xml 或 application.yml),因为日志框架本身无法感知你的业务重试了多少次。
最佳的解决方案是:在你的代码(代码逻辑或重试框架)中,根据当前的重试计数(Retry Count)来动态决定调用 logger.info()、logger.warn() 还是 logger.error()。
下面为你提供两种主流、优雅的实现方案:
方案一:使用 Spring Retry 框架(推荐,最优雅)
如果你使用的是 Spring Boot,Spring Retry 提供了强大的重试机制。你可以通过自定义一个 RetryListener(重试监听器),在每次失败时判断次数并输出对应级别的日志。
1. 引入依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
2. 自定义日志重试监听器
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.listener.RetryListenerSupport; import org.springframework.stereotype.Component; @Component public class LogRetryListener extends RetryListenerSupport { private static final Logger log = LoggerFactory.getLogger(LogRetryListener.class); @Override public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { int count = context.getRetryCount(); // 当前是第几次尝试失败(从 1 开始) if (count == 1) { // 第一次失败,打印 INFO log.info("远程API调用第 1 次失败,准备重试。异常信息: {}", throwable.getMessage()); } else if (count == 2) { // 第二次失败,打印 WARN log.warn("远程API调用第 2 次失败,最后一次重试机会!异常信息: {}", throwable.getMessage()); } // 注意:最后一次(第三次)彻底失败,不在这里打印 ERROR,而在 @Recover(兜底方法)里打印 } }
3. 在业务代码中使用
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Recover; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; @Service public class RemoteCallService { private static final Logger log = LoggerFactory.getLogger(RemoteCallService.class); // maxAttempts = 3 代表总共调用3次(1次正常调用 + 2次重试) // listeners 指定我们刚才写的日志监听器 @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000), listeners = "logRetryListener") public String callRemoteApi() { // 模拟你的远程 REST API 调用 // restTemplate.getForObject(...); throw new RuntimeException("连接超时"); } // 3次全部失败后,会进入这个兜底方法,此时打印 ERROR @Recover public String recover(RuntimeException e) { log.error("远程API调用经历2次重试后仍然彻底失败!触发报警。错误原因: ", e); return "默认降级数据"; } }
方案二:纯代码手动控制(不依赖框架,最直观)
如果你没有使用 Spring,或者不想引入复杂的重试框架,可以直接用一个简单的 for 循环加 try-catch 来手写这个逻辑:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ApiClient { private static final Logger log = LoggerFactory.getLogger(ApiClient.class); public String executeWithRetry() { int maxAttempts = 3; // 1次正常 + 2次重试 for (int attempt = 1; attempt <= maxAttempts; attempt++) { try { // 模拟调用 API return doCallApi(); } catch (Exception e) { if (attempt == 1) { // 第一次调用失败: e.getMessage(0 打印很干净,不占地方 log.info("远程API首次调用失败,进行第1次重试。错误: {}", e.getMessage()); } else if (attempt == 2) { // 第二次调用失败(第一次重试失败) log.warn("远程API重试失败,进行第2次重试。错误: {}", e.getMessage()); } else { // 第三次调用失败(第二次重试也失败了):会打印报错的整个堆栈信息,比如哪一行,调用链,底层驱动报错的异常都会有打印出来 log.error("远程API历经2次重试后仍然全部失败!", e); throw e; // 抛出异常或返回降级数据 } // 可以加一个短暂的等待再重试,比如 1 秒 try { Thread.sleep(1000); } catch (InterruptedException ignored) {} } } return null; } private String doCallApi() { throw new RuntimeException("网络连接异常"); } }
总结与建议
-
为什么不推荐在日志配置文件里配? 因为日志文件只能根据“包名”、“类名”或“特定的 Marker”来区分级别,它不知道你当前是在进行第几次重试。
-
选哪种? 如果你的项目已经是 Spring Boot 项目,强烈建议用 方案一(Spring Retry),代码侵入性极低,后续修改重试次数(比如改成重试 3 次)也非常好扩展。
-
在日志文件中的“待遇”不同(可以根据项目的日志管理框架去配置)
在生产环境中,日志配置文件(如
logback-spring.xml)通常会做日志分流:-
INFO日志:通常只保存在info.log中。因为流量大,为了防止硬盘被撑爆,可能只保留最近 7 天,过期的就自动删除了。 -
ERROR日志:通常会额外单独输出到一个error.log文件中,并且保留时间非常长(比如 30 天甚至 90 天)。
-

浙公网安备 33010602011771号