Programmatic configuration

Next to configuration files, Log4j Core can be configured programmatically too. In this page, we will explore utilities helping with programmatic configuration and demonstrate how they can be leveraged for certain use cases.

Preliminaries

To begin with, we strongly encourage you to check out the Architecture page first. Let’s repeat some basic definitions of particular interest:

LoggerContext

It is the anchor of the logging system. Generally there is one, statically-accessible, global LoggerContext for most applications. But there can be multiple LoggerContexts, for instance, to use in tests, in Java EE web applications, etc.

Configuration

It encapsulates a Log4j Core configuration (properties, appenders, loggers, etc.) and is associated with a LoggerContext.

Tooling

For programmatic configuration, Log4j Core essentially provides the following tooling:

ConfigurationBuilder

for declaratively creating a Configuration

Configurator

for associating a Configuration with a LoggerContext

ConfigurationFactory

for registering a Configuration factory to the configuration file mechanism

In short, we will create Configurations using ConfigurationBuilder, and activate them using Configurator.

ConfigurationBuilder

ConfigurationBuilder interface models a fluent API to programmatically create Configurations. If you have ever created a Log4j Core configuration file, consider ConfigurationBuilder as a convenience utility to model the very same declarative configuration structure programmatically.

Let’s show ConfigurationBuilder usage with an example. Consider the following Log4j Core configuration file:

  • XML

  • JSON

  • YAML

  • Properties

Snippet from an example log4j2.xml
<Appenders>
  <Console name="CONSOLE">
    <JsonTemplateLayout/>
  </Console>
</Appenders>

<Loggers>
  <Root level="WARN">
    <AppenderRef ref="CONSOLE"/>
  </Root>
</Loggers>
Snippet from an example log4j2.json
"Appenders": {
  "Console": {
    "name": "CONSOLE",
    "JsonTemplateLayout": {}
  }
},
"Loggers": {
  "Root": {
    "level": "WARN",
    "AppenderRef": {
      "ref": "CONSOLE"
    }
  }
}
Snippet from an example log4j2.yaml
Appenders:
  Console:
    name: "CONSOLE"
    JsonTemplateLayout: {}

Loggers:
  Root:
    level: "WARN"
    AppenderRef:
      ref: "CONSOLE"
Snippet from an example log4j2.properties
appender.0.type = Console
appender.0.name = CONSOLE
appender.0.layout.type = JsonTemplateLayout

rootLogger.level = WARN
rootLogger.appenderRef.0.ref = CONSOLE

Above Log4j Core configuration can be programmatically built using ConfigurationBuilder as follows:

Snippet from an example Usage.java
ConfigurationBuilder<BuiltConfiguration> configBuilder =
        ConfigurationBuilderFactory.newConfigurationBuilder(); (1)
Configuration configuration = configBuilder
        .add(
                configBuilder (2)
                        .newAppender("CONSOLE", "List")
                        .add(configBuilder.newLayout("JsonTemplateLayout")))
        .add(
                configBuilder (3)
                        .newRootLogger(Level.WARN)
                        .add(configBuilder.newAppenderRef("CONSOLE")))
        .build(false); (4)
1 The default ConfigurationBuilder instance is obtained using ConfigurationBuilderFactory.newConfigurationBuilder() static method
2 Add the appender along with the layout
3 Add the root logger along with a level and appender reference
4 Create the configuration, but don’t initialize it

It is a good practice to not initialize Configurations when they are constructed. This task should ideally be delegated to Configurator.

ConfigurationBuilder has convenience methods for the base components that can be configured such as loggers, appenders, filters, properties, etc. Though there are cases where the provided convenience methods fall short of:

For those, you can use the generic ConfigurationBuilder#newComponent() method.

See Configurator1Test.java for examples on ConfigurationBuilder, newComponent(), etc. usage.

Configurator

Configurator is a programmatic interface to associate a Configuration with either new, or an existing LoggerContext.

Obtaining a LoggerContext

You can use Configurator to obtain a LoggerContext:

Snippet from an example Usage.java
Configuration configuration = createConfiguration();
try (LoggerContext loggerContext = Configurator.initialize(configuration)) {
    // Use `LoggerContext`...
}

initialize() will either return the LoggerContext currently associated with the caller, or create a new one. This is a convenient way to create isolated LoggerContexts for tests, etc.

Reconfiguring the active LoggerContext

You can use Configurator to reconfigure the active LoggerContext as follows:

Snippet from an example Usage.java
Configuration configuration = createConfiguration();
Configurator.reconfigure(configuration);

Using the Configurator in this manner allows the application control over when Log4j is initialized. However, should any logging be attempted before Configurator.initialize() is called then the default configuration will be used for those log events.

ConfigurationFactory

ConfigurationFactory interface, which is mainly used by the configuration file mechanism to load a Configuration, can be leveraged to inject a custom Configuration. You need to

Consider the example below:

Snippet from an example ExampleConfigurationFactory.java
@Order(100)
@Plugin(name = "ExampleConfigurationFactory", category = ConfigurationFactory.CATEGORY)
public class ExampleConfigurationFactory extends ConfigurationFactory {

    @Override
    public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) { (1)
        // Return a `Configuration`...
    }

    @Override
    public Configuration getConfiguration(LoggerContext loggerContext, String name, URI configLocation) {
        // Return a `Configuration`...
    }

    @Override
    public String[] getSupportedTypes() {
        return new String[] {"*"};
    }
}
1 getConfiguration(LoggerContext, ConfigurationSource) is only called if ConfigurationSource is not null. This is possible if the Configuration is provided programmatically. Hence, you are encouraged to implement getConfiguration(LoggerContext, String, URI) overload too.

How-to guides

In this section we will share guides on programmatically configuring Log4j Core for certain use cases.

Loading a configuration file

ConfigurationFactory provides the getInstance() method returning a meta-ConfigurationFactory that combines the behaviour of all available ConfigurationFactory implementations, including the predefined ones; XmlConfigurationFactory, JsonConfigurationFactory, etc. You can use this getInstance() method to load a configuration file programmatically, granted that the input file format is supported by at least one of the available ConfigurationFactory plugins:

Snippet from an example Usage.java
ConfigurationFactory.getInstance()
        .getConfiguration(
                null, (1)
                null, (2)
                URI.create("uri://to/my/log4j2.xml")); (3)
1 Passing the LoggerContext argument as null, since this is the first time we are instantiating this Configuration, and it is not associated with a LoggerContext yet
2 Passing the configuration name argument as null, since it is not used when the configuration source location is provided
3 URI pointing to the configuration file; file://path/to/log4j2.xml, classpath:log4j2.xml, etc.

Combining multiple configurations

There are occasions where multiple configurations might need to be combined. For instance,

  • You have a common Log4j Core configuration that should always be present, and an environment-specific one that extends the common one depending on the environment (test, production, etc.) the application is running on.

  • You develop a framework, and it contains a predefined Log4j Core configuration. Yet you want to allow users to extend it whenever necessary.

  • You collect Log4j Core configurations from multiple sources.

You can programmatically combine multiple configurations into a single one using CompositeConfiguration:

Snippet from an example Usage.java
ConfigurationFactory configFactory = ConfigurationFactory.getInstance();
AbstractConfiguration commonConfig = (AbstractConfiguration) (2)
        configFactory.getConfiguration(null, null, URI.create("classpath:log4j2-common.xml")); (1)
AbstractConfiguration appConfig = (AbstractConfiguration) (2)
        configFactory.getConfiguration(null, null, URI.create("classpath:log4j2-app.xml")); (1)
AbstractConfiguration runtimeConfig = ConfigurationBuilderFactory.newConfigurationBuilder()
        // ...
        .build(false); (3)
return new CompositeConfiguration(Arrays.asList(commonConfig, appConfig, runtimeConfig)); (4)
1 Loading a common, and an application-specific configuration from file
2 Casting them to AbstractConfiguration, the type required by CompositeConfiguration
3 Programmatically creating an uninitialized configuration. Note that no casting is needed.
4 Creating a CompositeConfiguration using all three configurations created. Note that passed configuration order matters!
How does CompositeConfiguration work?

CompositeConfiguration merges multiple configurations into a single one using a MergeStrategy, which can be customized using the log4j2.mergeStrategy configuration property. The default merge strategy works as follows:

  • Global configuration attributes in later configurations replace those in previous configurations. The only exception is the monitorInterval attribute: the lowest positive value from all the configuration files will be used.

  • Properties are aggregated. Duplicate properties override those in previous configurations.

  • Filters are aggregated under CompositeFilter, if more than one filter is defined.

  • Scripts are aggregated. Duplicate definitions override those in previous configurations.

  • Appenders are aggregated. Appenders with the same name are overridden by those in later configurations, including all their elements.

  • Loggers are aggregated. Logger attributes are individually merged, and those in later configurations replace duplicates. Appender references on a logger are aggregated, and those in later configurations replace duplicates. The strategy merges filters on loggers using the rule above.

Modifying configuration components

We strongly advise against programmatically modifying components of a configuration! This section will explain what it is, and why you should avoid it.

It is unfortunately common that users modify components (appenders, filters, etc.) of a configuration programmatically as follows:

LoggerContext context = LoggerContext.getContext(false);
Configuration config = context.getConfiguration();
PatternLayout layout = PatternLayout.createDefaultLayout(config);
Appender appender = createCustomAppender();
appender.start();
config.addAppender(appender);
updateLoggers(appender, config);

This approach is prone several problems:

  • Your code relies on Log4j Core internals which don’t have any backward compatibility guarantees. You not only risk breaking your build at a minor Log4j Core version upgrade, but also make the life of Log4j maintainers trying to evolve the project extremely difficult.

  • You move out from the safety zone, where Log4j Core takes care of components' life cycle (initialization, reconfiguration, etc.), and step into a minefield seriously undermining the reliability of your logging setup.

If you happen to have code programmatically modifying components of a configuration, we advise you to migrate to other declarative approaches shared in this page. In case of need, feel free to ask for help in user support channels.