Ошибка вместо предупреждения при выходе из системы Ресурс [logback.xml] встречается несколько раз в пути к классам

Чтобы разделить конфигурацию logback между несколькими проектами, мы встраиваем наш файл logback.xml в общий jar. например, mylogger.jar. Проекты зависят от этого фляги для регистрации, следовательно, это всегда на пути к классам. Это означает, что logback.xml будет найден, как описано в

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

Однако, если другой jar, например, 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, так как это поведение недетерминировано в соответствии с управлением пути к классам в сервлете.

Есть ли какой-либо механизм, заставляющий предупреждение проваливать сборку? Это предупредит нас о вышеупомянутом сценарии, тогда как предупреждение может быть проигнорировано.

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's 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));
    }
} 

Вы, вероятно, видите эти события в журнале, потому что вы настроили Logback с <configuration debug="true">, С помощью 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" />

При наличии вышеуказанной регистрации и прослушивателя вы сможете перехватить "Ресурс... встречается несколько раз на пути к классам". события и предоставить свои собственные действия / ответ на них.

Мой вариант использования: несколько logback-test.xml файлы на пути к классу при запуске тестов

Я посмотрел на подход, предложенный @glytching в /questions/87224/oshibka-vmesto-preduprezhdeniya-pri-vyihode-iz-sistemyi-resurs-logbackxml-vstrechaetsya-neskolko-raz-v-puti-k-klassam/87237#87237, но с ним было две проблемы:

  1. Некоторые события создаются (в моем конкретном случае использования) до того, как Logback создаст экземпляр моего statusListener. В моем случае так было с конкретными событиями, на которые мы хотели здесь отреагировать.
  2. Выбрасывание исключений из прослушивателя состояния на самом деле не работает (оно не будет передано в вызывающий код, а будет перехвачено и обработано самим Logback).

На основе ch.qos.logback.core.status.OnPrintStreamStatusListenerBase class в Logback, я понял, как решить проблему 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 );
        }
    }
}
Другие вопросы по тегам