Custom ConfigurationFactory combined with configuration file in log4j2
Asked Answered
O

2

8

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? ;)

Overpowering answered 28/5, 2017 at 13:19 Comment(0)
R
7

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.
Roundworm answered 31/7, 2017 at 11:20 Comment(4)
This answer helps my problem in some way but my understanding was a bit different after looking at the documentation. Since as per the question, I intent to use a configuration file coupled with my own configuration factory, i figured Log4j2 can automatically load custom configuration factories based on the annotation.Eoin
Thanks for the thorough answer. This helped me track down my issue as well and gave me some other avenues to try. However, as you supposed above, I wasn't setting the custom logger early enough. I had completely overlooked a Logger which I assigned with inline initialization in the Main class. After moving that initialization, everything worked like a charm. For others experiencing problems with the ConfigurationFactory, be sure to double check that you aren't initializing Logger instances before setting the ConfigurationFactory!Overzealous
You see, this is what confuses me. I used did not use the steps in the end to include the ConfigurationFactory and for some reason it still works. Doing changes to the ConfigurationFactory results in changes in the logging. For some reason log4j2 seems to be able to find the configuration factory anyway. Any ideas how this works? Using log4j 2.9.0.Empoison
Are you using the @Order Annotation on your ConfigurationFactory? I believe log4j scans all classes for specific annotations and adds these classes automatically.Roundworm
S
0

I think this may be due to the @Order() you have. I couldn't find any documentation that explained how the specified orders are compared, but my belief is that higher numbers are used by preferrence. The default XmlConfigurationFactory has an order of 5 (log4j version 2.13.1) so if you change your order to be 6 you should get your factory as you require.

Singular answered 16/4, 2020 at 10:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.