Configuring Log4J2 in OSGi Environment

Unlike Log4J 1.x Log4J 2 comes with API separation with it being broken down into log4j-core and log4j-api. Log4J-core contains the implementation and the log4j-api can be used as a logging facade providing the adapter components required for implementers to create a logging implementation.

Due to the separation, Log4J 2 is not directly implementable in an OSGi environment. This is due to the separate classloaders loading the different bundles. Nonetheless, it can be configured in an OSGi environment in two ways.

Solutions

  1. Using the ServiceLoader interface
  2. Using pax-logging

I would go for the second and thus this piece of writing will cover the way Log4J2 is configured with pax-logging. But before that, let me explain why not the first.

Making use of the ServiceLoader will require implementing Log4J 2 as a service provider and the bundles consuming as service consumers, and if you have used a different logging facade other than log4j like jcl(Jakarta Commons Logging) you might as well need the log4j bridge in the classpath. Not only that, you also need a workaround using two OSGi fragments: one for log4j-core and one for log4j-api. Due to different classloaders, log4j-core cannot identify the configuration file in its classpath and therefore a fragment should be created with the host being log4j-core with the configuration file packed inside of it. The log4j-api needs the log4j-provider.properties (residing in log4j-core bundle) in its classpath and hence another fragment with the host being log4-api has to provide this. If you have custom appenders, then these too have to go as a fragment of log4j-core. So you need the log4j-core, log4j-api, the bridge jar depending on your logging front-end (e.g. log4j-jcl) and at least two OSGi fragments.

Using pax-logging

Pax-logging is a logging framework that supports OSGi and thus eliminates the need for ServiceLoader and the workaround for classloading issues. Pax-logging provides the pax-logging-api which contains most of the logging facades. For the logging back-end it provides the pax-logging-log4j2 bundle. If you need to configure custom appenders for Log4J2, they should go into a fragment of pax-logging-log4j bundle.


Setting up Log4J2 with pax-logging in the OSGi environment

  1. Add the dependencies into the pom file of the bundle.
<dependency>
<groupId>org.ops4j.pax.logging</groupId>
<artifactId>pax-logging-api</artifactId>
<version>1.10.1</version>
</dependency>
<dependency>
<groupId>org.ops4j.pax.logging</groupId>
<artifactId>pax-logging-log4j2</artifactId>
<version>1.10.1</version>
</dependency>

2. Load the pax-logging-api-1.10.1.jar and pax-logging-log4j2–1.10.1.jar into the OSGi environment

3. Configure logging

Pax-logging has pax-logging.properties configuration file. It also allows to use the log4j2 configuration file. For this, you need to add an entry into pax-logging.properties to get the log4j2 configurations as shown below.

org.ops4j.pax.logging.log4j2.config.file=path/to/log4j2.properties

4. Writing custom appenders

Log4J 2 allows to extend and customize in a number of ways using Plugins. For adding a custom field you can use a converter or a lookup plugin. For appending to a destination of your own, you may use the appender plugin type (See manual for Extending Log4J2 for more). All or any of these should be written in a OSGi fragment bundle of pax-logging-log4j2. You can find an example below.

A. Write a custom appender

@Plugin(name = "MemoryAppender", category = "Core", elementType = "appender", printObject = true)
public final class MemoryAppender extends AbstractAppender {
 private MemoryAppender(final String name, final Filter filter,
final Layout<? extends Serializable> layout,
final boolean ignoreExceptions)
{
super(name, filter, layout, ignoreExceptions);
}

@PluginFactory
public static MemoryAppender createAppender(
@PluginAttribute("name") final String name,
@PluginElement("Filters") final Filter filter,
@PluginElement("Layout") Layout<? extends Serializable> layout,
@PluginAttribute("ignoreExceptions") final String ignore)
{

if (name == null) {
LOGGER.error("No name provided for MemoryAppender");
return null;
} else {
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
final boolean ignoreExceptions =
Booleans.parseBoolean(ignore, true);
return new MemoryAppender(name,filter,layout,ignoreExceptions);
}
}

@Override
public void append(LogEvent logEvent)
{
//append to detination
}
}

B. Add the fragment host and plugin processor into the pom.xml

<Fragment-Host>org.ops4j.pax.logging.pax-logging-log4j2</Fragment-Host>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>log4j-plugin-processor</id>
<goals>
<goal>compile</goal>
</goals>
<phase>process-classes</phase>
<configuration>
<proc>only</proc>
<annotationProcessors>
<annotationProcessor>
org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</execution>
</executions>
</plugin>

Build the bundle and you should see the Plugins.dat file created inside META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat of the jar. If it does not exist, then the resources are not packed by default. To solve this you need to specify to include the resource in the pom as shown in the line below.

<Include-Resource>${project.build.directory}/classes/</Include-Resource>

C. Define the appender in the configuration file

appenders = MEMORY
appender.CARBON_MEMORY.type = MemoryAppender
appender.CARBON_MEMORY.name
= MEMORY
appender.CARBON_MEMORY.layout.type
= PatternLayout
appender.CARBON_MEMORY.layout.pattern
= [%d] %5p {%c} — %m%ex%n

That’s it! Log4J 2 is configured in OSGi.

Like what you read? Give Asma Zinneera Jabir a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.