记录一次日志告警随着nacos文件动态刷新而失效的问题

           事情是这样的:我把告警的配置修改成了随着nacos动态更新的,但是更新后日志异常告警却不打印了。刚开始怀疑是不是网络或者nacos配置的问题,经过复现发现是因为我每次修改完nacos动态配置后,我的日志组件TurboFilter 是基于日志logback,他不是spring的bean的方式加载的,而nacos的动态更新需要bean是spring的,当我在nacos更新后,日志组件没有动态加载导致失效了。

 

   我的原来的代码:

      

/**
 * 异常日志堆栈-用于非运行时异常的告警提示
 * @author fanht
 * @date 2022-03-21  17:28
 * @versio 1.0
 */
@Component
public class MarketingLogFilter extends TurboFilter {

    /**rocketmq异常信息打印*/
    private static final String ROCKETMQ_ERROR = "rocketmq";

    @Resource
    private SimpleBufferTriggerUtils simpleBufferTriggerUtils;

    @Resource
    private OptionalAlarmUriUtils optionalAlarmUriUtils;

    @Resource
    private DingdingAlarmUtil dingdingAlarmUtil;
    @Override
    public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
        if(logger.getName().contains("rocketmq")){
            System.out.println("mq error:" + (Optional.of(logger).map(l->l.getName()).orElse(null))+ ",throwable:" + Optional.ofNullable(throwable).map(t->t.getMessage()).orElse(null));
        }
        if(dingdingAlarmUtil==null){
            dingdingAlarmUtil = ApplicationContext.getBean(DingdingAlarmUtil.class);
        }
        //todo 如果是非运行时异常 发送钉钉告警。排除运行时异常
        if(throwable != null && StringUtils.isNotEmpty(throwable.getMessage()) && level.equals(Level.ERROR)){
            if(optionalAlarmUriUtils == null){
                optionalAlarmUriUtils = ApplicationContext.getBean(OptionalAlarmUriUtils.class);
            }
            if(!optionalAlarmUriUtils.isExistUri(logger.getName())){
                if(simpleBufferTriggerUtils == null){
                    simpleBufferTriggerUtils =  ApplicationContext.getBean(SimpleBufferTriggerUtils.class);
                }
                simpleBufferTriggerUtils.proceErrorAlarm(Tag.builder().applicationName(dingdingAlarmUtil.getApplicationName())
                        .env(dingdingAlarmUtil.getEnv())
                        .ip(IpUtil.initIp())
                        .traceId(MDC.get(TraceUtils.TRACE_ID))
                        .requestUri(logger.getName())
                        .exMessage(JSONObject.toJSON(throwable.getMessage().length() >500 ?
                                throwable.getMessage().substring(0,500).toString():throwable.getMessage()).toString())
                        .build());
            }
        }else if(Level.ERROR.equals(level) && logger.getName().contains(ROCKETMQ_ERROR)){
            //如果是mq的异常 也要进行告警
            if(!optionalAlarmUriUtils.isExistUri(logger.getName())){
                String exMessage = Optional.ofNullable(s).orElse("") + Optional.ofNullable(objects).map(t->t[0]).orElse(null);
                simpleBufferTriggerUtils.proceErrorAlarm(Tag.builder().applicationName(dingdingAlarmUtil.getApplicationName())
                        .env(dingdingAlarmUtil.getEnv()).ip(IpUtil.initIp()).requestUri(logger.getName()).traceId(MDC.get(TraceUtils.TRACE_ID))
                        .exMessage(Optional.ofNullable(exMessage).map(t->t.length()>500?t.substring(0,500):t).orElse(""))
                        .build());
            }
        }
        //todo 如果接入kafka,则此处需要需要改为NEUTRAL,以防止kakfa的多次打印
        return FilterReply.NEUTRAL;
    }
}

 

         

   借助ai工具,问题记录如下:

 

      

Spring Cloud 的 Logging Refresh 会重建 Logback 上下文

当你调用 /actuator/refresh(或 Nacos 自动触发 refresh)时,Spring Cloud 会刷新 logging 相关配置(如 logging.level.*logging.file.name 等)。

而 Logback 的实现方式是:重建整个 LoggerContext

这意味着:

  • 原有的 LoggerContext 被关闭
  • 所有已注册的 AppenderTurboFilterLogger 都被清除
  • 创建一个新的 LoggerContext
  • 重新加载 logback-spring.xml

 

         

这就是为什么刷新后 decide() 完全不执行 —— filter 根本不在新上下文中。


✅ 解决方案:监听 LoggerContext 重建事件,自动重新注册

Logback 提供了 LoggerContextListener 机制,但更简单的方式是:监听 Spring 的 ApplicationEnvironmentPreparedEventContextRefreshedEvent 不行,因为 logging refresh 是独立的。

正确做法:监听 Logback 自身的 ContextResetEvent

但 Spring Boot 封装了 Logback,我们可以通过 监听 Spring 的 LoggingApplicationListener 行为 来间接处理。

不过最可靠、最通用的方法是:

每次 Logback 初始化时自动注册你的 TurboFilter

✅ 推荐方案:自定义 LoggerContextListener

创建一个 Logback 的 listener,在每次上下文初始化时注册 filter:

  

 最佳方案(不改 XML):

监听 RefreshScopeRefreshedEvent,并在新 LoggerContext 初始化后重新注册 TurboFilter

关键点:

  • Spring Cloud 在 /actuator/refresh 后会发布 RefreshScopeRefreshedEvent
  • 此时 Logback 的 LoggerContext 可能已经被重建
  • 我们可以在事件监听器中 重新获取当前 LoggerContext 并注册 filter

 

 

Component
public class TurboFilterReRegistrar {

    private final MarketingLogFilter marketingLogFilter;

    public TurboFilterReRegistrar(MarketingLogFilter marketingLogFilter) {
        this.marketingLogFilter = marketingLogFilter;
    }

    /**
     * 监听 Refresh 完成事件(包括 Nacos 配置刷新)
     */
    @EventListener
    public void handleRefresh(RefreshScopeRefreshedEvent event) {
        reRegisterTurboFilter();
    }

    /**
     * 也监听 Context Refreshed(兜底,确保启动时也注册)
     */
    @EventListener
    public void handleContextRefreshed(ContextRefreshedEvent event) {
        reRegisterTurboFilter();
    }

    private void reRegisterTurboFilter() {
        try {
            ILoggerFactory factory = LoggerFactory.getILoggerFactory();
            if (factory instanceof LoggerContext) {
                LoggerContext context = (LoggerContext) factory;

                // 检查是否已存在,避免重复注册(TurboFilter 没有 equals/hashCode,简单用类型判断)
                boolean alreadyRegistered = context.getTurboFilterList().stream()
                    .anyMatch(f -> f.getClass() == MarketingLogFilter.class);

                if (!alreadyRegistered) {
                    context.addTurboFilter(marketingLogFilter);
                    System.out.println("✅ Re-registered MarketingLogFilter after refresh");
                } else {
                    // 如果已存在,可能是同一个实例,无需操作
                    System.out.println("ℹ️ MarketingLogFilter already registered");
                }
            }
        } catch (Exception e) {
            System.err.println("❌ Failed to re-register TurboFilter: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

   验证通过,没问题了

posted @ 2025-12-29 16:03  Doyourself!  阅读(23)  评论(0)    收藏  举报