Using log4j2 (2.3) with Android

The versatile log4j is a popular logging framework for Java. Log4j 2 brings new advanced features and extensibility. Unfortunately, out of the box, it’s not very Android friendly. I spent a good day trying to perfect the usage and integration. Given the difficulties and workarounds required, I would hesitate to use Log4j2 for any Android project. This blog post is part commentary, part guide. If you really want to use it, check out my log4j2-android github for an example project. Hopefully this helps somebody out there.

The latest versions of log4j require Java 7. Since Android does not have full support for Java 7 when your app compatibility is under API 19, the latest version you could use that’s Java 6 compatible is log4j 2.3. This is the version that we will use.

Add these dependencies to your app build.gradle:

dependencies {
    ...
    compile 'org.apache.logging.log4j:log4j-api:2.3'
    compile 'org.apache.logging.log4j:log4j-core:2.3'

Reflection Everywhere!!

One the annoying things I found was that Log4j 2 used reflection heavily, especially on app start up when it tries to initialise all its plugins. The effect is that app load time is slightly longer. Reflection also means you need to put exceptions for proguard.

LogcatAppender

Log4j allows you to configure different appenders. Common appenders are the ConsoleAppender and FileAppender. ConsoleAppender uses the Java System.err and System.out to print log messages. In Android, they get routed to Logcat, but they will all be in the INFO category and have no ability to specify a tag. With log4j, you could create your own appender plugins, so I’ve defined a LogcatAppender. The appender maps logger.error to Log.e, logger.debug to Log.d, etc etc and uses the logger name as the tag.

AndroidLookup

Another type of plugin you could create is a Lookup. Lookups are custom variables which you can reference in your log config files. When specifying the file path to your log file, a lookup value that will come in handy is the android data directory. The AndroidLookup I’ve created allows you to use ${android:filesdir}, ${android:externalfilesdir} or ${android:logfilesdir} placeholder variables.

Configuration

By default log4j searches and loads its configuration from the file system using a bunch of rules. This is not ideal in an Android environment where the app is packaged an an APK. The default behaviour can be overridden by providing a custom ConfigurationSource to the LoggerContext. I’ve put the log4j XML configuration in a raw resource and implemented a custom ContextSelector to initialise the LoggerContext with the custom config. All of this is then tied together by the AndroidLog4jHelper class I’ve created.

AndroidLog4jHelper also injects plugins

There is another purpose to AndroidLog4jHelper and that is to inject plugins. As mentioned, Log4j uses reflection heavily. One of the problems is that Android/ART/Dalvik is not Java, and hence some assumptions that are made in Log4j, based on the JRE, aren’t applicable to Android. Typically, the PluginManager.addPackage method is used to register plugins. It works by finding the package folder in the .jar and enumerating the .class files. Then it would use ClassLoader and reflection to find the @Plugin annotations to register. In Android, we don’t have .class files in a .jar (or apk), hence it doesn’t find anything. To make the LogcatAppender and AndroidLookup plugins work correctly, I’ve devised a hack – Using reflection, I manually injected the plugins to a privately held Map in the PluginRegistry. It would be helpful if Log4j natively provide a method to manually register plugins by passing the class type.

Using AndroidLog4jHelper and Plugins

The key classes for this log4j android solution are in this package folder. Copy them to your project.

To use the AndroidLog4jHelper, in your Application sub class onCreate method, put:

if (BuildConfig.DEBUG) {
    AndroidLog4jHelper.initialise(this.getApplicationContext(), R.raw.log4j_debug);
} else {
    AndroidLog4jHelper.initialise(this.getApplicationContext(), R.raw.log4j_release);
}

Note that we are specifying different XML configs for debug and release. If you only require one config, then you don’t need to have the if condition. Create the raw resources log4j_debug.xml and log4j_release.xml. An example XML config which uses LogcatAppender and AndroidLookup:

<?xml version="1.0" encoding="UTF-8"?>

<Configuration status="debug">
    <Appenders>
        <Logcat name="Logcat">
            <ThresholdFilter level="ALL" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%m" />
        </Logcat>
        <RollingFile name="RollingFile" fileName="${android:logfilesdir}/app.log" filePattern="app-%i.log.gz">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="1 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <Root level="DEBUG">
            <AppenderRef ref="Logcat"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

In the class in which you want to log, put:

    private static final Logger logger = LogManager.getLogger("MyClass");

Then you can use:

    logger.info("Log4j2 succcess");

Using the config above, you should see the log messages appear in Logcat. Messages will also be logged to the filesystem, under [your ext data directory]/logs/app.log

Proguard rules for Log4j

When releasing the APK to the public, a best practise is to utilise proguard to reduce the size of the file and obfuscate methods from sticky beaks. Since Log4j uses reflection, here are the proguard rules required for it to run properly. The presence of -keepattributes Signature may compromise code obfuscation, but unfortunately is required for log4j to work properly.

-dontwarn org.apache.logging.log4j.**
-keepattributes Signature
-keep class org.apache.logging.log4j.** { *; }
-keep class net.loune.log4j2android.** { *; }

Exclude miscellaneous META-INF files

If you want to avoid build errors when generating an APK (You do!!!), you need to exclude some dupe’d META-INF files that are present in the log4j2 jar files. Modify your app build.gradle as such:

android {
    ...

    packagingOptions {
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/DEPENDENCIES'
    }

In conclusion, Log4j2 could be comfortably used with Android, but with some compromises. If the start up performance and proguard rules are acceptable to you, and you like the extensibility of the various L4J plugins, then Log4j2 might be the logging solution for you.

8 thoughts on “Using log4j2 (2.3) with Android

  1. Antonio Otero

    Hello.
    I’ve tried to use all of this stuff, and it doesn’t work for me.
    The very first time I run de app I get this message on logcat:

    06-06 15:13:30.261: I/dalvikvm(2880): Could not find method org.osgi.framework.FrameworkUtil.getBundle, referenced from method org.apache.logging.log4j.core.config.plugins.util.ResolverUtil.loadImplementationsInBundle

    A couple of lines below I can see this message on LogCat:
    06-06 15:13:32.611: W/System.err(2880): ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console…

    And nothing is logger either on LogCat or in file.

    Do yoy have any idea or hint to be able to get this working?

    Thanks a lot beforehand

  2. Loune Post author

    @Antonio Otero, I’m not able to reproduce the issue. What devices/settings are you using? I just tried with Android Studio 2.1.2 / JDK 1.8 on Mac OS X and running it on Android 6.0 emulator (debug mode) and works fine.

  3. Antonio Otero

    Hello!
    I’m using Eclipse Luna 4.4.2, maven with simpligility android plugin on Samsung Galaxy Active with Android 4.4.4.
    The first time with Log4J 2.6, then I realised the example was made with Log4J 2.3 so I tried with this version but with the same results.

    Thank you very much for help

  4. Loune Post author

    The log messages sound like some dependencies are missing. I’m not sure how to configure it with Eclipse, but make sure that you’re including org.apache.logging.log4j:log4j-api:2.3 and org.apache.logging.log4j:log4j-core:2.3 If you’re using proguard, make sure the proguard rules above are applied. Failing that, maybe try using Android Studio instead of Eclipse.

  5. Antonio Otero

    I’ll take a look once again to everything, review proguard rules and I’ll let you know again.

    Thanks a lot for your help

  6. Jose Vaisman

    I’m running into the same issue. I used the example you publish on github, with no modifications

  7. leo

    Hello.
    I’ve tried to use your log4j2 for android, and it work .But, when i create a mudule and put log4j-core-2.3.jar,log4j-api-2.3.jar,and your AndroidContextSelector.java,AndroidLookup.java etc. it, the project
    transformClassesAndResourcesWithSyncLibJarsForRelease FAILED
    Error:Execution failed for task ‘:log4j2lib:transformClassesAndResourcesWithSyncLibJarsForRelease’.
    > java.util.zip.ZipException: duplicate entry: META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat

    i try some way ,but all can work,so i need your help.Do you have any idea or hint to be able to let it working?

    Thanks a lot beforehand

  8. Loune Post author

    @leo have you tried adding
    packagingOptions {
    exclude ‘META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat’
    }
    under the android section of your app build.gradle?

Leave a Reply

Your email address will not be published. Required fields are marked *