How to configure Log4J programmatically in two steps?
About
This article shows you how to configure Log4j 2.2 programmatically by extending the actual configuration.
Why extend the actual configuration?
Because Log4J gives the possibility to reload a configuration file. If this were the case, the programmatic configuration would be lost.
Steps
Steps 1: Programmatic Configuration
In this step, we create a configuration that extends an Xml Configuration (ie Log4j.xml)
- We add a logger called TEST_LOGGER
- that gets two programmatically created Appenders:
- a file Appender
- a console Appender
public class Log4JConfiguration extends XmlConfiguration {
public static final String TEST_LOGGER = Log4JConfiguration.class.getName();
public Log4JConfiguration(LoggerContext loggerContext, ConfigurationSource source) {
super(loggerContext, source);
}
@Override
protected void doConfigure() {
super.doConfigure();
PatternLayout defaultLayout = PatternLayout.createDefaultLayout();
FileAppender fileAppender = FileAppender.newBuilder()
.setName("TestLogFile")
.setConfiguration(this)
.withFileName("test.log")
.setLayout(defaultLayout)
.setImmediateFlush(true)
.build();
this.addAppender(fileAppender);
ConsoleAppender console = ConsoleAppender.newBuilder()
.setName("TestConsole") // mandatory
.setConfiguration(this)
.setTarget(ConsoleAppender.Target.SYSTEM_OUT)
.build();
testLogger.addAppender(console, null, null);
LoggerConfig testLogger = LoggerConfig.newBuilder()
.withLoggerName(TEST_LOGGER)
.withAdditivity(false)
.withConfig(this)
.withLevel(Level.INFO)
.build();
// Add the appenders to the logger
testLogger.addAppender(fileAppender, null, null);
testLogger.addAppender(myFileAppender,null, null);
// Add the logger to the configuration
addLogger(testLogger.getName(), testLogger);
}
}
Step 2: Advertise it with a ConfigurationFactory
The below code is a factory that:
- is registered via the @Plugin
- return the previous Log4JConfiguration
When a Log4j logger is asked for the first time, it invokes this factory and build the configuration.
@Plugin(name = "Log4jConfigurationFactoryPlugin", category = ConfigurationFactory.CATEGORY)
@Order(10)
public class Log4jConfigurationFactoryPlugin extends ConfigurationFactory {
/**
* Valid file extensions for XML files.
*/
public static final String[] SUFFIXES = new String[]{".xml", "*"};
/**
* Return the Configuration.
*
* @param source The InputSource.
* @return The Configuration.
*/
@Override
public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) {
return new Log4JConfiguration(loggerContext, source);
}
/**
* Returns the file suffixes for XML files.
*
* @return An array of File extensions.
*/
public String[] getSupportedTypes() {
return SUFFIXES;
}
}
Annexes
Remark on Appenders
Note that Appenders created with the builder factory (and not with the Node class representing the file loaded) does not support out-of-the-box lookups to uses variables from the context.
You may then: * add them in your original configuration and * also create a new log4j configuration with new Appenders (See below)
How to load another log4j configuration
You can do it:
- with the xinclude syntax
- via the composite configuration where the log4j2.configurationFile property contains a list of files separated by comma.
- or programmatically
LoggerContext loggerContext = this.getLoggerContext();
// Get your configuration as string
String result = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Configuration>....</Configuration>";
InputStream stream = new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8));
ConfigurationSource configurationSource = new ConfigurationSource(stream);
Configuration currentReadConfiguration = ConfigurationFactory.getInstance().getConfiguration(loggerContext, configurationSource);
currentReadConfiguration.initialize();
// then you can access the appenders
Appender appender = currentReadConfiguration.getAppender("myAppenderName");