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 multipleLoggerContext
s, 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 aLoggerContext
-
ConfigurationFactory
-
for registering a
Configuration
factory to the configuration file mechanism
In short, we will create Configuration
s using ConfigurationBuilder
, and activate them using Configurator
.
ConfigurationBuilder
ConfigurationBuilder
interface models a fluent API to programmatically create Configuration
s.
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
log4j2.xml
<Appenders>
<Console name="CONSOLE">
<JsonTemplateLayout/>
</Console>
</Appenders>
<Loggers>
<Root level="WARN">
<AppenderRef ref="CONSOLE"/>
</Root>
</Loggers>
log4j2.json
"Appenders": {
"Console": {
"name": "CONSOLE",
"JsonTemplateLayout": {}
}
},
"Loggers": {
"Root": {
"level": "WARN",
"AppenderRef": {
"ref": "CONSOLE"
}
}
}
log4j2.yaml
Appenders:
Console:
name: "CONSOLE"
JsonTemplateLayout: {}
Loggers:
Root:
level: "WARN"
AppenderRef:
ref: "CONSOLE"
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:
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
|
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:
-
Custom plugins that are declared to be represented in a configuration
-
Custom subcomponents (e.g., a triggering policy for rolling file appenders)
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
:
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 LoggerContext
s for tests, etc.
Reconfiguring the active LoggerContext
You can use Configurator
to reconfigure the active LoggerContext
as follows:
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
-
Assign it a higher priority (i.e., higher
@Order
value) -
Support all configuration file types (i.e. return
*
fromgetSupportedTypes()
)
Consider the example below:
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:
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
:
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.