Ошибка вместо предупреждения при выходе из системы Ресурс [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, но с ним было две проблемы:
- Некоторые события создаются (в моем конкретном случае использования) до того, как Logback создаст экземпляр моего
statusListener
. В моем случае так было с конкретными событиями, на которые мы хотели здесь отреагировать. - Выбрасывание исключений из прослушивателя состояния на самом деле не работает (оно не будет передано в вызывающий код, а будет перехвачено и обработано самим 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 );
}
}
}