<

I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we’ll take a look at different ways to programmatically configure Apache Log4j 2.

2. Initial Setup

To start using Log4j 2, we merely need to include the log4j-core and log4j-slf4j-impl dependencies in our pom.xml:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.11.0</version>
</dependency>

3. ConfigurationBuilder

Once we have Maven configured, then we need to create a ConfigurationBuilder, which is the class that lets us configure appenders, filters, layouts, and loggers.

Log4j 2 provides several ways to get a ConfigurationBuilder.

Let’s start with the most direct way:

ConfigurationBuilder<BuiltConfiguration> builder
 = ConfigurationBuilderFactory.newConfigurationBuilder();

And to begin configuring components, ConfigurationBuilder is equipped with a corresponding new method, like newAppender or newLayout, for each component.

Some components have different subtypes, like FileAppender or ConsoleAppender, and these are referred to in the API as plugins.

3.1. Configuring Appenders

Let’s tell the builder where to send each log line by configuring an appender:

AppenderComponentBuilder console 
  = builder.newAppender("stdout", "Console"); 

builder.add(console);

AppenderComponentBuilder file 
  = builder.newAppender("log", "File"); 
file.addAttribute("fileName", "target/logging.log");

builder.add(file);

While most new methods don’t support this, newAppender(name, plugin) allows us to give the appender a name, which will turn out to be important later on. These appenders, we’ve called stdout and log, though we could’ve named them anything.

We’ve also told the builder which appender plugin (or, more simply, which kind of appender) to use. Console and File refer to Log4j 2’s appenders for writing to standard out and the file system, respectively.

Though Log4j 2 supports several appenders, configuring them using Java can be a bit tricky since AppenderComponentBuilder is a generic class for all appender types.

This makes it have methods like addAttribute and addComponent instead of setFileName and addTriggeringPolicy:

AppenderComponentBuilder rollingFile 
  = builder.newAppender("rolling", "RollingFile");
rollingFile.addAttribute("fileName", "rolling.log");
rollingFile.addAttribute("filePattern", "rolling-%d{MM-dd-yy}.log.gz");
rollingFile.addComponent(triggeringPolicies);

builder.add(rollingFile);

And, finally, don’t forget to call builder.add to append it to the main configuration!

3.2. Configuring Filters

We can add filters to each of our appenders, which decide on each log line whether it should be appended or not.

Let’s use the MarkerFilter plugin on our console appender:

FilterComponentBuilder flow = builder.newFilter(
  "MarkerFilter", 
  Filter.Result.ACCEPT,
  Filter.Result.DENY);  
flow.addAttribute("marker", "FLOW");

console.add(flow);

Note that this new method doesn’t allow us to name the filter, but it does ask us to indicate what to do if the filter passes or fails.

In this case, we’ve kept it simple, stating that if the MarkerFilter passes, then ACCEPT the logline. Otherwise, DENY it.

Note in this case that we don’t append this to the builder but instead to the appenders that we want to use this filter.

3.3. Configuring Layouts

Next, let’s define the layout for each log line. In this case, we’ll use the PatternLayout plugin:

LayoutComponentBuilder standard 
  = builder.newLayout("PatternLayout");
standard.addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable");

console.add(standard);
file.add(standard);
rolling.add(standard);

Again, we’ve added these directly to the appropriate appenders instead of to the builder directly.

3.4. Configuring the Root Logger

Now that we know where logs will be shipped to, we want to configure which logs will go to each destination.

The root logger is the highest logger, kind of like Object in Java. This logger is what will be used by default unless overridden.

So, let’s use a root logger to set the default logging level to ERROR and the default appender to our stdout appender from above:

RootLoggerComponentBuilder rootLogger 
  = builder.newRootLogger(Level.ERROR);
rootLogger.add(builder.newAppenderRef("stdout"));

builder.add(rootLogger);

To point our logger at a specific appender, we don’t give it an instance of the builder. Instead, we refer to it by the name that we gave it earlier.

3.5. Configuring Additional Loggers

Child loggers can be used to target specific packages or logger names.

Let’s add a logger for the com package in our application, setting the logging level to DEBUG and having those go to our log appender:

LoggerComponentBuilder logger = builder.newLogger("com", Level.DEBUG);
logger.add(builder.newAppenderRef("log"));
logger.addAttribute("additivity", false);

builder.add(logger);

Note that we can set additivity with our loggers, which indicates whether this logger should inherit properties like logging level and appender types from its ancestors.

3.6. Configuring Other Components

Not all components have a dedicated new method on ConfigurationBuilder.

So, in that case, we call newComponent.

For example, because there isn’t a TriggeringPolicyComponentBuilder, we need to use newComponent for something like specifying our triggering policy for rolling file appenders:

ComponentBuilder triggeringPolicies = builder.newComponent("Policies")
  .addComponent(builder.newComponent("CronTriggeringPolicy")
    .addAttribute("schedule", "0 0 0 * * ?"))
  .addComponent(builder.newComponent("SizeBasedTriggeringPolicy")
    .addAttribute("size", "100M"));
 
rolling.addComponent(triggeringPolicies);

3.7. The XML Equivalent

ConfigurationBuilder comes equipped with a handy method to print out the equivalent XML:

builder.writeXMLConfiguration(System.out);

Running the above line prints out:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
   <Appenders>
      <Console name="stdout">
         <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable" />
         <MarkerFilter onMatch="ACCEPT" onMisMatch="DENY" marker="FLOW" />
      </Console>
      <RollingFile name="rolling" 
        fileName="target/rolling.log" 
        filePattern="target/archive/rolling-%d{MM-dd-yy}.log.gz">
         <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable" />
         <Policies>
            <CronTriggeringPolicy schedule="0 0 0 * * ?" />
            <SizeBasedTriggeringPolicy size="100M" />
         </Policies>
      </RollingFile>
      <File name="FileSystem" fileName="target/logging.log">
         <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable" />
      </File>
   </Appenders>
   <Loggers>
      <Logger name="com" level="DEBUG" additivity="false">
         <AppenderRef ref="log" />
      </Logger>
      <Root level="ERROR" additivity="true">
         <AppenderRef ref="stdout" />
      </Root>
   </Loggers>
</Configuration>

This comes in handy when we want to double-check our configuration or if we want to persist our configuration, say, to the file system.

3.8. Putting it all Together

Now that we are fully configured, let’s tell Log4j 2 to use our configuration:

Configurator.initialize(builder.build());

After this is invoked, future calls to Log4j 2 will use our configuration.

Note that this means that we need to invoke Configurator.initialize before we make any calls to LogManager.getLogger.

4. ConfigurationFactory

Now that we’ve seen one way to get and apply a ConfigurationBuilder, let’s take a look at one more:

public class CustomConfigFactory
  extends ConfigurationFactory {
 
    public Configuration createConfiguration(
      LoggerContext context, 
      ConfigurationSource src) {
 
        ConfigurationBuilder<BuiltConfiguration> builder = super
          .newConfigurationBuilder();

        // ... configure appenders, filters, etc.

        return builder.build();
    }

    public String[] getSupportedTypes() { 
        return new String[] { "*" };
    }
}

In this case, instead of using ConfigurationBuilderFactory, we subclassed ConfigurationFactory, an abstract class targetted for creating instances of Configuration.

Then, instead of calling Configurator.initialize like we did the first time, we simply need to let Log4j 2 know about our new configuration factory.

There are three ways to do this:

  • Static initialization
  • A runtime property, or
  • The @Plugin annotation

4.1. Use Static Initialization

Log4j 2 supports calling setConfigurationFactory during static initialization:

static {
    ConfigurationFactory custom = new CustomConfigFactory();
    ConfigurationFactory.setConfigurationFactory(custom);
}

This approach has the same limitation as for the last approach we saw, which is that we’ll need to invoke it before any calls to LogManager.getLogger.

4.2. Use a Runtime Property

If we have access to the Java startup command, then Log4j 2 also supports specifying the ConfigurationFactory to use via a -D parameter:

-Dlog4j2.configurationFactory=com.baeldung.log4j2.CustomConfigFactory

The main benefit of this approach is that we don’t have to worry about initialization order as we do with the first two approaches.

4.3. Use the @Plugin Annotation

And finally, in circumstances where we don’t want to fiddle with the Java startup command by adding a -D, we can simply annotate our CustomConfigurationFactory with the Log4j 2 @Plugin annotation:

@Plugin(
  name = "CustomConfigurationFactory", 
  category = ConfigurationFactory.CATEGORY)
@Order(50)
public class CustomConfigFactory
  extends ConfigurationFactory {

  // ... rest of implementation
}

Log4j 2 will scan the classpath for classes having the @Plugin annotation, and, finding this class in the ConfigurationFactory category, will use it.

4.4. Combining with Static Configuration

Another benefit to using a ConfigurationFactory extension is that we can easily combine our custom configuration with other configuration sources like XML:

public Configuration createConfiguration(
  LoggerContext context, 
  ConfigurationSource src) {
    return new WithXmlConfiguration(context, src);
}

The source parameter represents the static XML or JSON configuration file that Log4j 2 finds if any.

We can take that configuration file and send it to our custom implementation of XmlConfiguration where we can place whatever overriding configuration we need:

public class WithXmlConfiguration extends XmlConfiguration {
 
    @Override
    protected void doConfigure() {
        super.doConfigure(); // parse xml document

        // ... add our custom configuration
    }
}

5. Conclusion

In this article, we looked at how to use the new ConfigurationBuilder API available in Log4j 2.

We also took a look at customizing ConfigurationFactory in combination with ConfigurationBuilder for more advanced use cases.

Don’t forget to check out my complete examples over on GitHub.

I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE LESSONS

Leave a Reply

avatar
  Subscribe  
Notify of