【问题标题】:Error instead of warn on logback Resource [logback.xml] occurs multiple times on the classpath在类路径上多次出现错误而不是警告 logback 资源 [logback.xml]
【发布时间】:2019-01-23 22:30:30
【问题描述】:

为了在多个项目之间共享 logback 配置,我们将 logback.xml 文件嵌入到一个公共 jar 中。例如mylogger.jar。项目依赖这个 jar 进行日志记录,因此它总是在类路径中。这意味着 logback.xml 将按照记录在

中找到

https://logback.qos.ch/manual/configuration.html#auto_configuration

但是,如果另一个罐子,例如otherlib.jar,还嵌入了一个 logback.xml 文件,我们会看到一个警告

09:27:03,122 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
09:27:03,122 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
09:27:03,122 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [jar:file:/WEB-INF/lib/mylogger.jar/logback.xml]
09:27:03,123 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs multiple times on the classpath.
09:27:03,123 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/WEB-INF/lib/mylogger.jar/logback.xml]
09:27:03,123 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/WEB-INF/lib/otherlib.jar/logback.xml]

更糟糕的是,有时它不会选择正确的 logback.xml,因为根据Controlling the classpath in a servlet,这种行为是不确定的。

是否有任何机制强制警告使构建失败?这将提醒我们注意上述情况,而可以忽略警告。

【问题讨论】:

    标签: java runtime-error classpath logback


    【解决方案1】:

    在初始化期间,Logback 发出 Status 事件来描述正在发生的事情。这些...

    09:27:03,123 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs multiple times on the classpath.
    09:27:03,123 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/WEB-INF/lib/mylogger.jar/logback.xml]
    09:27:03,123 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/WEB-INF/lib/otherlib.jar/logback.xml]
    

    ... 是一些Status 事件的日志语句。这些Status 事件由Logback 的ContextInitializer 发出...

    if (urlSet != null && urlSet.size() > 1) {
        sm.add(new WarnStatus("Resource [" + resourceName + "] occurs multiple times on the classpath.", loggerContext));
        for (URL url : urlSet) {
            sm.add(new WarnStatus("Resource [" + resourceName + "] occurs at [" + url.toString() + "]", loggerContext));
        }
    } 
    

    您可能会看到记录了这些事件,因为您已使用 <configuration debug="true"> 配置 Logback。使用debug=true 相当于安装OnConsoleStatusListener

    您可以注册一个自定义StatusListener,它对这些Status 事件的反应不同。鉴于您想要“强制警告使构建失败”,那么当您的StatusListener 遇到“资源......在类路径上多次出现”时,您可以抛出异常。事件。

    这是一个(未经测试的)示例:

    import ch.qos.logback.core.status.Status;
    import ch.qos.logback.core.status.StatusListener;
    
    public class StrictConfigurationWarningStatusListener implements StatusListener {
        @Override
        public void addStatusEvent(Status status) {
            if (status.getEffectiveLevel() == Status.WARN) {
                // you might want to consider how best to evaluate whether this is the message you are interested in
                // this approach is bound to a string and hence will no longer work if Logback changes this message
                if (status.getMessage().endsWith("occurs multiple times on the classpath.")) {
                    throw new LogbackException(status.getMessage());
                }
            }
        }
    }
    

    您可以在logback.xml注册您的听众,如下:

    <statusListener class="some.package.StrictConfigurationWarningStatusListener" />
    

    通过上述注册和监听器,您将能够拦截“资源......在类路径上多次出现”。事件并提供您自己的操作/响应。

    【讨论】:

      【解决方案2】:

      我的用例:运行测试时类路径上有多个logback-test.xml文件

      我查看了@glytching 在https://stackoverflow.com/a/51891708/227779 中建议的方法,但它有两个问题:

      1. 创建了一些事件(在我的特定用例中)之前 Logback 实例化了我的statusListener。在我的例子中,我们希望在这里对特定事件做出反应。
      2. 从状态侦听器中抛出异常实际上并不起作用(它不会传播到调用代码,而是由 Logback 本身捕获和处理)。

      基于 Logback 中的ch.qos.logback.core.status.OnPrintStreamStatusListenerBase 类,我想出了如何解决问题 1。

      为了解决问题 2,我添加了一个我填充的静态 EVENTS 字段,然后在侦听器中添加了一个 ensureEventsIsEmpty 方法。然后我可以像这样从静态初始化程序(在我的情况下是单元测试类)中调用它:

      public class SomeTestClass {
          private static final Logger logger = LoggerFactory.getLogger( SomeTestClass.class );
      
          static {
              StrictConfigurationWarningStatusListener.ensureNoEventsReceived();
          }
      
          // ...test methods goes here.
      }
      

      调整StrictConfigurationWarningStatusListener实现

      package some.package;
      
      import java.util.List;
      import java.util.concurrent.CopyOnWriteArrayList;
      
      import ch.qos.logback.classic.LoggerContext;
      import ch.qos.logback.core.spi.ContextAwareBase;
      import ch.qos.logback.core.spi.LifeCycle;
      import ch.qos.logback.core.status.Status;
      import ch.qos.logback.core.status.StatusListener;
      import ch.qos.logback.core.status.StatusManager;
      
      public class StrictConfigurationWarningStatusListener extends ContextAwareBase implements StatusListener, LifeCycle {
          private static final List<String> EVENTS = new CopyOnWriteArrayList<>();
      
          private boolean isStarted = false;
      
          public static void ensureNoEventsReceived() {
              if ( !EVENTS.isEmpty() ) {
                  for ( String event : EVENTS ) {
                      System.err.println( event );
                  }
      
                  throw new IllegalStateException( "Multiple logback-test.xml files found on the classpath." );
              }
          }
      
          @Override
          public void addStatusEvent( Status status ) {
              if ( !isStarted ) {
                  // Events being posted at this stage will be retrieved from the StatusManager in the
                  // retrospectivelyHandleEvents() method.
                  return;
              }
      
              handleEvent( status );
          }
      
          @Override
          public void start() {
              isStarted = true;
      
              retrospectivelyHandleEvents();
          }
      
          @Override
          public void stop() {
              isStarted = false;
          }
      
          @Override
          public boolean isStarted() {
              return isStarted;
          }
      
          private void handleEvent( Status status ) {
              if ( status.getEffectiveLevel() == Status.WARN ) {
                  if ( status.getOrigin() instanceof LoggerContext &&
                          ( status.getMessage().contains( "occurs multiple times" ) ||
                                  status.getMessage().contains( "occurs at" ) ) ) {
      
                      EVENTS.add( status.getMessage() );
                  }
              }
          }
      
          private void retrospectivelyHandleEvents() {
              StatusManager statusManager = context.getStatusManager();
              List<Status> statusList = statusManager.getCopyOfStatusList();
      
              for ( Status status : statusList ) {
                  handleEvent( status );
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2014-03-16
        • 1970-01-01
        • 2015-08-27
        • 1970-01-01
        • 1970-01-01
        • 2012-06-11
        • 2015-05-11
        • 2018-12-23
        • 1970-01-01
        相关资源
        最近更新 更多