Spring Boot基于Bean或AOP的异常处理方案选型咨询
Spring Boot异常场景下统一发送监控指标的最佳实践
核心问题拆解
需要在**所有异常发生时(包括try/catch捕获的异常和未捕获被@ControllerAdvice兜底的异常)**统一发送监控指标,避免在每个业务类中重复注入MetricsBean,同时不破坏现有代码结构。
推荐方案
方案1:Spring AOP 切面拦截日志方法(最通用)
这是你考虑的选项3,也是最符合"通用无侵入"需求的方案。通过AOP拦截org.slf4j.Logger的error方法,在日志记录前后调用MetricsBean发送指标。
实现步骤:
- 确保项目引入Spring AOP依赖(Spring Boot默认已包含)
- 编写AOP切面类:
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Aspect @Component public class ErrorLogMetricsAspect { @Autowired private MetricsBean metricsBean; // 定义切入点:拦截指定包下所有SLF4J Logger的error方法 @Pointcut("execution(* org.slf4j.Logger.error(..)) && within(com.yourpackage..*)") public void errorLogPointcut() {} // 日志记录完成后发送指标 @AfterReturning(pointcut = "errorLogPointcut()") public void afterErrorLog(JoinPoint joinPoint) { // 提取异常参数 Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof Throwable) { // 根据异常类型发送对应指标 metricsBean.recordExceptionMetric(((Throwable) arg).getClass().getSimpleName()); break; } } // 针对无异常的error日志发送通用错误指标 metricsBean.recordGeneralErrorMetric(); } }
- 注意事项:
- 用
within表达式缩小拦截范围,避免统计框架内部的错误日志 - 区分带异常参数和不带异常参数的
error方法,避免误统计 - 确保
MetricsBean的方法是线程安全的,适配多线程调用场景
- 用
方案2:封装自定义日志工具类(侵入性低,可控性强)
创建Spring管理的日志工具类,封装Logger和MetricsBean的逻辑,业务类直接使用该工具类代替原生Logger。
实现步骤:
- 编写日志工具类:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class LogAndMetricsUtil { @Autowired private MetricsBean metricsBean; public void error(Class<?> clazz, String message, Throwable throwable) { Logger logger = LoggerFactory.getLogger(clazz); logger.error(message, throwable); // 发送异常指标 metricsBean.recordExceptionMetric(throwable.getClass().getSimpleName()); } // 重载方法,简化无消息参数的调用 public void error(Class<?> clazz, Throwable throwable) { error(clazz, throwable.getMessage(), throwable); } }
- 业务类中注入并使用:
@Service public class YourService { @Autowired private LogAndMetricsUtil logAndMetricsUtil; public void doBusiness() { try { // 业务逻辑 } catch (Exception ex) { logAndMetricsUtil.error(this.getClass(), ex); } } }
- 优势:
- 完全可控,仅统计业务代码主动调用的日志
- 可灵活扩展日志与指标的关联逻辑,比如添加自定义标签
- 不会被框架内部日志干扰
方案3:扩展日志框架的Appender(全局统计)
如果使用Logback/Log4j2,可自定义Appender,在日志输出时触发指标发送,无需Spring AOP,直接通过日志框架扩展实现。
Logback示例:
- 编写自定义Appender:
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MetricsAppender extends AppenderBase<ILoggingEvent> { @Autowired private MetricsBean metricsBean; @Override protected void append(ILoggingEvent event) { // 仅处理ERROR级别日志 if (event.getLevel().isGreaterOrEqual(ch.qos.logback.classic.Level.ERROR)) { Throwable throwable = event.getThrowableProxy() != null ? event.getThrowableProxy().getThrowable() : null; if (throwable != null) { metricsBean.recordExceptionMetric(throwable.getClass().getSimpleName()); } else { metricsBean.recordGeneralErrorMetric(); } } } }
- 在
logback-spring.xml中配置:
<configuration> <appender name="METRICS" class="com.yourpackage.MetricsAppender"/> <root level="INFO"> <appender-ref ref="METRICS"/> <!-- 其他已配置的appender --> </root> </configuration>
- 优势:
- 完全无侵入,无需修改业务代码
- 覆盖所有日志框架输出的ERROR日志,包括框架内部异常
方案对比与选择
| 方案 | 侵入性 | 可控性 | 适用场景 |
|---|---|---|---|
| Spring AOP | 低 | 中 | 需要拦截特定包/类的日志,或与Spring上下文深度结合 |
| 自定义日志工具类 | 中(需替换业务类的Logger) | 高 | 仅统计业务代码主动捕获的异常,需要灵活扩展逻辑 |
| 日志框架Appender | 无 | 低 | 全局统计所有ERROR级日志,包括框架异常 |
补充说明
@ControllerAdvice中的兜底异常已可直接使用MetricsBean,需注意避免与上述方案重复统计(比如在AOP/Appender中排除该类的日志,或不在@ControllerAdvice中记录ERROR日志)
内容的提问来源于stack exchange,提问作者hitchhiker
相关产品推荐
相关产品推荐

