Search code examples
javalog4j

How to read log4j 2 configuration programmatically?


I'm working with a third-party log4j v2 Appender which gives me the opportunity to plug-in some custom classes of my own (the Appender will create those via Reflection).

In other words: My class gets instantiated from the Appender which again gets instantiated as part of log4j's normal bootstrap. I do not have control over the Appender which is from another library.

I would like a few properties for my own custom class to be read from the Log4j configuration. It makes sense that it lives there since it is indeed related to logging.

However, I cannot figure out how to do this. At the moment I do something like this from the constructor of my custom class:

LoggerContext loggerContext = LoggerContext.getContext(true);
if (loggerContext != null) {
    Configuration config = loggerContext.getConfiguration();
    if (config != null) {
        StrSubstitutor strSubstitutor = config.getStrSubstitutor();
        if (strSubstitutor != null) {
            StrLookup variableResolver = strSubstitutor.getVariableResolver();
            if (variableResolver != null) {
                String myVar = variableResolver.lookup("myPropertyName");
            }
        }
    }
}

but the problem seems to be that at this point in time (during bootstrap) the Log4j configuration is indeed not yet initialized so

LoggerContext.getContext(true).getConfiguration()

returns an instance of DefaultConfiguration (which obviously doesn't have my property) where I would have expected an instance of XmlConfiguration. The latter I get when I call LoggerContext.getContext(true).getConfiguration() at any later point in time and I can indeed read my custom properties from that one.


Solution

  • As you guessed, and as stated in the Log4j architecture documentation when they describe LoggerContext Configuration:

    Every LoggerContext has an active Configuration. The Configuration contains all the Appenders, context-wide Filters, LoggerConfigs and contains the reference to the StrSubstitutor. During reconfiguration two Configuration objects will exist. Once all Loggers have been redirected to the new Configuration, the old Configuration will be stopped and discarded.

    This reconfiguration process is performed in the LoggerContext start method, which in turn calls reconfigure, and finally setConfiguration.

    Pay attention to the following line in setConfiguration:

    firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
    

    As you can see, LoggerContext will propagate the change in the configuration to the configured java.beans.PropertyChangeListeners.

    This approach is followed in the own library in the Log4jBridgeHandler class. In its init method you can see:

    if (propagateLevels) {
        @SuppressWarnings("resource")    // no need to close the AutoCloseable ctx here
        LoggerContext context = LoggerContext.getContext(false);
        context.addPropertyChangeListener(this);
        propagateLogLevels(context.getConfiguration());
        // note: java.util.logging.LogManager.addPropertyChangeListener() could also
        // be set here, but a call of JUL.readConfiguration() will be done on purpose
    }
    

    And they define the appropriate propertyChange event handler:

    @Override
    // impl. for PropertyChangeListener
    public void propertyChange(PropertyChangeEvent evt) {
        SLOGGER.debug("Log4jBridgeHandler.propertyChange(): {}", evt);
        if (LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())  &&  evt.getNewValue() instanceof Configuration) {
            propagateLogLevels((Configuration) evt.getNewValue());
        }
    }
    

    I think you can follow a similar approach in your component. Please:

    1. Make it implement java.beans.PropertyChangeListener.
    2. In the corresponding propertyChange event handler method, reconfigure your component as required:
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())  &&  evt.getNewValue() instanceof Configuration) {
            Configuration config = (Configuration) evt.getNewValue();
            if (config != null) {
                StrSubstitutor strSubstitutor = config.getStrSubstitutor();
                if (strSubstitutor != null) {
                    StrLookup variableResolver = strSubstitutor.getVariableResolver();
                    if (variableResolver != null) {
                        String myVar = variableResolver.lookup("myPropertyName");
                    }
                }
            }
        }
    }
    
    1. In its constructor or initialization code, register your component as a listener for the reconfiguration event:
    LoggerContext context = LoggerContext.getContext(false);
    context.addPropertyChangeListener(this);