Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom ConfigurationFactory combined with configuration file in log4j2

I'm trying to Initialize Log4j by Combining Configuration File with Programmatic Configuration.

I followed the manual (though it's syntax isn't quite right and it's outdated), which resulted in the following classes:

CustomConfigurationFactory.java:

package factory;

import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
import org.apache.logging.log4j.core.config.plugins.Plugin;

import java.net.URI;

@Plugin(name = "CustomConfigurationFactory", category = ConfigurationFactory.CATEGORY)
@Order(1)
public class CustomConfigurationFactory extends ConfigurationFactory {

    /**
     * Valid file extensions for XML files.
     */
    private static final String[] SUFFIXES = new String[]{".xml", "*"};

    /**
     * Return the Configuration.
     *
     * @param source The InputSource.
     * @return The Configuration.
     */
    public Configuration getConfiguration(LoggerContext context, ConfigurationSource source) {

        return new CustomConfiguration(context, source);

    }

    /**
     * Returns the file suffixes for XML files.
     * @return An array of File extensions.
     */
    public String[] getSupportedTypes() {

        return SUFFIXES;

    }

}

CustomConfiguration.java:

package factory;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;

import java.util.Map;

public class CustomConfiguration extends XmlConfiguration {

    CustomConfiguration(LoggerContext context, ConfigurationSource configSource) {

        super(context, configSource);

    }

    @Override
    protected void doConfigure() {

        super.doConfigure();

        final LoggerConfig rootLogger = getRootLogger();

        final Map<String, Appender> appenderMap = rootLogger.getAppenders();

        if (MainClass.DEBUG) {

            rootLogger.addAppender(appenderMap.get("Console"), Level.ALL, null);

        } else {

            rootLogger.addAppender(appenderMap.get("Mail"), Level.ERROR, null);

        }

    }
}

Now, when running this and calling ConfigurationFactory.setConfigurationFactory(new CustomConfigurationFactory()) before any calls to the Logging API, I'm getting output to the console in the form of

ERROR StatusLogger Reconfiguration failed: No configuration found for 'someNumbersAndChars' at 'null' in 'null'

While debugging this, I found out that this is printed the first time I'm acquiring a Logger. The reason for that is that, if a custom ConfigurationFactory is supplied, the implementation of ConfigurationFactory.getConfiguration(LoggerContext, String, URI) by ConfigurationFactory's private subclass Factory (which is the default factory) will be overridden by ConfigurationFactory's implementation.

And ConfigurationFactory's implementation simply returns null if the URI is so, while ConfigurationFactory.Factory's implementation nevertheless returns a valid configuration.

(link to source)

My first idea now would be to override these overloads of ConfigurationFactory.getConfiguration() in my custom factory, but there has to be another way, right? ;)

like image 349
Makk0 Avatar asked May 28 '17 13:05

Makk0


1 Answers

I solved this problem by calling

System.setProperty("log4j.configurationFactory", CustomConfigurationFactory.class.getName());

As an alternative use this JVM start parameter:
-Dlog4j.configurationFactory=factory.CustomConfigurationFactory

instead of

ConfigurationFactory.setConfigurationFactory(new CustomConfigurationFactory());

before accessing the LogManager for the first time.

-

EDIT: You can also configure this setting in the file log4j2.component.properties.

Content:

log4j.configurationFactory=factory.CustomConfigurationFactory

By doing so you can be sure this setting is applied before any logger classes are loaded and avoid the potential problems of class initialization order.

If you look for usage of org.apache.logging.log4j.util.PropertiesUtil in the Log4j2 sources you can find all settings that can be configured that way.

-

While analyzing / debugging the problem i noticed that my ConfigurationFactory was created, but not used to get the config.

Finally i found this passage in the docs, which explains it all (we probably did not call setConfigurationFactory early enough):

During initialization, Log4j 2 will search for available ConfigurationFactories and then select the one to use. The selected ConfigurationFactory creates the Configuration that Log4j will use. Here is how Log4j finds the available ConfigurationFactories:

  1. A system property named "log4j.configurationFactory" can be set with the name of the ConfigurationFactory to be used.
  2. ConfigurationFactory.setConfigurationFactory(ConfigurationFactory) can be called with the instance of the ConfigurationFactory to be used. This must be called before any other calls to Log4j.
  3. A ConfigurationFactory implementation can be added to the classpath and configured as a plugin in the "ConfigurationFactory" category. The Order annotation can be used to specify the relative priority when multiple applicable ConfigurationFactories are found.
like image 118
Frederic Leitenberger Avatar answered Sep 23 '22 11:09

Frederic Leitenberger