Search code examples
javaruntime-errorclasspathlogback

Error instead of warn on logback Resource [logback.xml] occurs multiple times on the classpath


To share logback configuration between multiple projects we embed our logback.xml file within a common jar. e.g. mylogger.jar. Projects depend upon this jar for logging hence it's always on the classpath. This means the logback.xml will be found as documented at

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

However if another jar, e.g. otherlib.jar, also embeds a logback.xml file we'll see a warning

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]

Worse sometimes it does not pick the correct logback.xml as this behavior is non-deterministic according to Controlling the classpath in a servlet.

Is there any mechanism to force the warning to fail a build? This'll alert us to the above scenario whereas a warning can be ignored.


Solution

  • During initialisation Logback emits Status events to describe what's happening. These ...

    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]
    

    ... are log statements for some of the Status events. These Status events are emitted by 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));
        }
    } 
    

    You're likely seeing those events logged because you have configured Logback with <configuration debug="true">. Using debug=true is equivalent to installing an OnConsoleStatusListener.

    You could register a custom StatusListener which reacts to these Status events differently. Given that you want to "force the warning to fail a build" then you could throw an exception when your StatusListener encounters the "Resource ... occurs multiple times on the classpath." event.

    Here's an (untested) example:

    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());
                }
            }
        }
    }
    

    You can register your listener in logback.xml as follows:

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

    With the above registration and listener in place you'll be able to intercept the "Resource ... occurs multiple times on the classpath." events and provide your own action/response to them.