错误而不是警告 logback 资源 [logback.xml] 在类路径上多次出现
Error instead of warn on logback Resource [logback.xml] occurs multiple times on the classpath
为了在多个项目之间共享 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,这种行为是不确定的。
是否有任何机制可以强制警告使构建失败?这将提醒我们注意上述情况,而可以忽略警告。
在初始化期间,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
事件的反应不同。鉴于您想要 "force the warning to fail a build" 那么当您的 StatusListener
遇到 "Resource ... occurs multiple times on the classpath." 事件时您可以抛出异常。
这是一个(未经测试的)示例:
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" />
通过上述注册和侦听器,您将能够拦截 "Resource ... occurs multiple times on the classpath." 事件并向它们提供您自己的 action/response。
我的用例:运行 测试时 class 路径上有多个 logback-test.xml
文件
我查看了@glytching 在 中建议的方法,但它有两个问题:
- 创建了一些事件(在我的特定用例中)在 Logback 实例化我的
statusListener
之前。就我而言,这就是我们想要在此处做出反应的特定事件的情况。
- 从状态侦听器中抛出异常实际上不起作用(它不会传播到调用代码,但会被 Logback 本身捕获和处理)。
基于Logback中的ch.qos.logback.core.status.OnPrintStreamStatusListenerBase
class,我想出了解决问题1的方法。
为了解决问题 2,我添加了一个我填充的静态 EVENTS
字段,然后在侦听器中添加了一个 ensureEventsIsEmpty
方法。然后我可以从静态初始化程序调用它(在我的例子中是单元测试 class),如下所示:
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 );
}
}
}
为了在多个项目之间共享 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,这种行为是不确定的。
是否有任何机制可以强制警告使构建失败?这将提醒我们注意上述情况,而可以忽略警告。
在初始化期间,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
事件的反应不同。鉴于您想要 "force the warning to fail a build" 那么当您的 StatusListener
遇到 "Resource ... occurs multiple times on the classpath." 事件时您可以抛出异常。
这是一个(未经测试的)示例:
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" />
通过上述注册和侦听器,您将能够拦截 "Resource ... occurs multiple times on the classpath." 事件并向它们提供您自己的 action/response。
我的用例:运行 测试时 class 路径上有多个 logback-test.xml
文件
我查看了@glytching 在
- 创建了一些事件(在我的特定用例中)在 Logback 实例化我的
statusListener
之前。就我而言,这就是我们想要在此处做出反应的特定事件的情况。 - 从状态侦听器中抛出异常实际上不起作用(它不会传播到调用代码,但会被 Logback 本身捕获和处理)。
基于Logback中的ch.qos.logback.core.status.OnPrintStreamStatusListenerBase
class,我想出了解决问题1的方法。
为了解决问题 2,我添加了一个我填充的静态 EVENTS
字段,然后在侦听器中添加了一个 ensureEventsIsEmpty
方法。然后我可以从静态初始化程序调用它(在我的例子中是单元测试 class),如下所示:
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 );
}
}
}