The Log4j2 framework is a drastic improvement over Log4j. Rolling file appenders work well across multiple JVMs, it offers improved reliability and it has a nice plugin system that enables you to extend the framework.
Those, and the fact that Log4j will not work in Java 9, are good reasons to migrate to Log4j2.
For regular webapps, setting up Log4j2 is easy. Just drop log4j2-api.jar
and log4j2-core.jar
on your classpath (using your favourite build system or otherwise), put a log4j2.xml
configuration on the classpath, and you’re ready to go.
The problem with Log4j2 in OSGi
In an OSGi context however, things become a little more tricky. To understand why, we need to look at how OSGi classloading works.
In an OSGi system, each bundle (typically a JAR with extra information in its manifest) has its own classloader. Consider a bundle B that depends on packageA
present in bundle A. For bundle B to access packageA
, bundle B must explicitly declare a dependency in its own manifest:
Import-Package: mypackageA;version="x.x.x"
Bundle A must in its turn specify that it exports
packageA:
Export-Package: mypackageA;version="x.x.x"
This allows OSGi frameworks like Apache Felix or Eclipse Equinox to provide different class versions to different client bundles. Different versions of the same class are located in separate bundles, each with their own classloader.
This presents a problem for Log4j2, however, because of the way Log4j2 configurates itself.
The first step Log4j2 takes is log4j-api
instantiating a LoggerContextFactory
implementation. Which one it should instantiate is specified in a file called log4j-provider.properties
, located in META-INF in log4j-core
.
In a regular application, META-INF is on the classpath so it can be found without any problems. But in OSGi, this file resides in the classloader of log4j-core
. The META-INF directory is not exported by log4j-core
so log4j-api
has no way of accessing it.
This typically results in the error:
log4j2.xml not found by org.apache.logging.log4j.core ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
The second problem is finding the
log4j2.xml
configuration file and custom plugins, like Appenders you have written yourself. As described In the Log4j2 plugin manual, log4j2 searches active OSGi bundles and tries to load plugin metadata from there.
However, log4j2 can be initialized quite early in the process, which means not all bundles are active yet.
The solution: use OSGi fragments!
What if there was a way to:
- Make the
log4j-provider.properties
available to thelog4j-api
artifact, and - Make the configuration file and plugins available to the
log4j-core
.
That would solve our problems, right?
Thankfully, we can do this. We’re going to build two OSGi fragments.
An OSGi fragment is like an OSGi bundle (eg a JAR file with some metadata in its manifest), but doesn’t have its own Activator. Instead it is part of another OSGi bundle.
In order to specify what OSGi bundle a fragment belongs to, specify the fragment host in the fragment’s manifest:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Log4j API configurator Bundle-SymbolicName: org.apache.logging.log4j.apiconf Bundle-Version: 1.0.0 Bundle-Vendor: None Bundle-RequiredExecutionEnvironment: OSGi/Minimum-1.2 Fragment-Host: org.apache.logging.log4j.api DynamicImport-Package: *
In this case, the fragment’s host bundle is the Log4j2 api artifact. This tells the OSGi framework to merge the contents of your fragment JAR with the Log4j2 Api OSGi bundle.
The fragment-host
value references the Bundle-SymbolicName
in the manifest of the log4j2-api artifact, which is org.apache.logging.log4j.api
. Note that the Bundle-SymbolicName
of the fragment can be anything you want it to be.
You can build an OSGi fragment with Maven, for example. A typical pom file for the example above would look like this:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>my.group</groupId> <artifactId>log4j2-api-config</artifactId> <version>1.0</version> <name>log4j2-api-config</name> <packaging>bundle</packaging> <properties> <java-version>1.7</java-version> </properties> <dependencies> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.0.0</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName>org.apache.logging.log4j.apiconf</Bundle-SymbolicName> <Bundle-Name>Log4j API Configurator</Bundle-Name> <Bundle-Version>1.0.0</Bundle-Version> <Fragment-Host>org.apache.logging.log4j.api</Fragment-Host> <DynamicImport-Package> *;resolution:=optional </DynamicImport-Package> </instructions> </configuration> </plugin> </plugins> <resources> <resource> <directory>.</directory> <includes> <include>log4j-provider.properties</include> </includes> <targetPath>META-INF</targetPath> </resource> </resources> </build> </project>
Fixing the api OSGi bundle
Now, all we need to do is to include everything we missed in the log4j2-api artifact. In this case, it’s just the log4j-provider.properties
file (you can just copy it from the one in the log4j2-core.jar
). So, the final fragment will look like this:
META-INF/ MANIFEST.MF log4j-provider.properties
Fixing the core OSGi bundle
To register plugins, we can also make a fragment that attaches itself to the log4j2-core
bundle, in the same way we did with the api bundle:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Log4j API configurator Bundle-SymbolicName: org.apache.logging.log4j.coreconf Bundle-Version: 1.0.0 Bundle-Vendor: None Bundle-RequiredExecutionEnvironment: OSGi/Minimum-1.2 Fragment-Host: org.apache.logging.log4j.core DynamicImport-Package: *
Now, we can add a
log4j2.xml
configuration file and needed plugins. For example, the following plugin appender and configuration file:@Plugin(name = "MyAppender", category = "Core", elementType = "appender", printObject = true) public class MyAppender extends AbstractAppender { public MyAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions) { super(name, filter, layout); } public void append(LogEvent logEvent) { // do something with the logevent } @PluginFactory public static MyAppender createAppender(@PluginAttribute("name") String name, @PluginElement("Layout") Layout<? extends Serializable> layout, @PluginElement("Filter") final Filter filter, @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) { return new MyAppender(name, filter, layout, ignoreExceptions); } }
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="debug" monitorInterval="60"> <Appenders> <MyAppender name="myAppender"> <PatternLayout pattern="%c %d{ISO8601} -- %p -- %m%n"/> </MyAppender> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="myAppender"/> </Root> </Loggers> </Configuration>
In order to make the plugin work, you need to generate a
log4jPlugins.dat
file that contains information about the plugin. The .dat file is automatically generated by the log4j plugin annotation processor when you compile the plugin with log4j2-core on the classpath.
All you need to do is to package these the files in an OSGi fragment. The final fragment looks like this:
META-INF/ MANIFEST.MF org/apache/logging/log4j/core/plugins/Log4j2Plugins.dat org/myplugins/MyAppender.class log4j2.xml
Now, just drop the two fragment JARs where the other OSGi bundles in your application reside. When you start your OSGi application you will see all log4j2 calls being handled as expected.
Since the log4j2 OSGi bundles have no explicit coupling with your fragments, you can leave the original bundles untouched.
Summary
Getting Log4j2 to work in an OSGi context can be a little tricky. Thankfully we can use OSGi fragments to extend the Log4j2 bundles and make things work:
Thanks for you post! I’ve found that log4j 2.8 no longer requires the fragment for log4j-api with the providers. It is enough to provide the fragment for log4j-core with the configuration.
Thanks! Took me some time, but finally got it working! Perfect solution!
Also thanks to Pedro Lamarao for suggesting that “It is enough to provide the fragment for log4j-core with the configuration.”!!