SLF4J and Logback in Android

When entering the world of Android development as a Java developer you will find a lot of things are familiar. But there are still a few things that are done differently. Logging, for example. Android provides its own logging API that can be easily used without any further configuration. This approach has its limits for more advanced projects, however. It is here that we can make use of our existing Java knowledge and libraries.

The original article with the title “Android Logging for Java Professionals – SLF4J and Logback in Android” from the “Javamagazin” is available here for download.

Happily for newcomers to Android, the static methods of the class android.util.Log can be called up to make entries in the system log without any additional configuration required. These entries can be viewed using the Logcat tool via the Android Debug Server (DDMS) or directly via an Android Debug Bridge (ADB) shell.

Although android.util.Log provides convenient basic logging functionality, it is not a satisfactory solution for advanced purposes such as configuration, integration of library logs, access to application logs, etc.

Many Java developers solve the problem by using SLF4J and Logback. The following will show you what these frameworks do and how they can be used in Android.

About SLF4J and Logback

Its simple API and the option of using a single standard configuration for all log statements, even those from 3rd party code, means that the Simple Logging Facade for Java (SLF4J) has developed into the defacto standard logging API for Java [see Weiss, Tal: „We Analyzed 30,000 GitHub Projects – Here Are The Top 100Libraries in Java, JS and Ruby“].

SLF4J is a lean API that allows log statements to be created in code. Bridges also exist to route log statements created by other logging frameworks through SLF4J. Which logging framework – that is, which implementation of the SLF4J API is to be used – must be decided only at the time of deployment.

Figure 1 shows the relationship between the layers and modules in the context of SLF4J. The individual modules are available as separate JAR files. The layers are labelled according to the SLF4J documentation [see Legacy APIs]. Caution: The modules in Log4J Version 2.0 are named differently: The bridging module (log4j-to-slf4j) is called “Adapter” and the adaptation layer module (log4j-slf4j-impl) is known as the “Binding” or “Bridge” [see log4j2 Dependencies].

Various other bridging modules exist, such as those for

  • Log4j 1.x (log4j-over-slf4j),
  • Log4j 2.x (log4j-to-slf4j),
  • Jakarta Commons Logging (jcl-over-slf4j) and
  • Java Util Logging (jul-to-slf4j).

A range of logging frameworks that implement the SLF4J API are available, such as

  • Logback, the native implementation of SLF4J,
  • its fork logback-android,
  • SLF4J Android, as well as adapter modules for
  • Log4j 1.x (slf4j-log4j12),
  • Log4j 2.x (log4j-slf4j-impl),
  • Java Util Logging (slf4j-jdk14),
  • Jakarta Commons Logging (slf4j-jcl) and others.

Abstraction via the SLF4J API allows the actual logging framework to be easily replaced without having to make any changes to code.

Passing all log statements through the uniform SLF4J API means that the logging output of all libraries in use can be configured in a standardised way.

Configuration is not part of SLF4J but is implemented by the logging framework.

In the case of Logback and logback-android, loggers are defined in your code to which data sinks, so-called appenders (e.g. files standard out or the Android system log) are assigned. Log statements are formatted within the appender. Filters that set the log level to be output can be set for each appender and/or for the loggers defined in the code.

Fig. 1: SLF4J control flow, modules and layers

SLF4J in Android

The benefits of SLF4J can also be used via Android. The following options are available:

  • Java Util Logging (JUL) has been part of the Java Development Kit since Java 1.4 and is also available in the Android Software Development Kit (SDK).
  • SLF4J provides a lightweight implementation of the API (slf4j-android, see Figure 1) that simply forwards all log statements to the Android logging system [see slf4j-android].
  • An implementation of Log4j 1.x exists for Android (android-logging-log4), but no longer appears to be maintained [see Logging with Log4J in Android].
  • An Android implementation of Logback is also available (logback-android, see Figure 1) [see logback-android].

We will not get into the hotly debated topic of whether Logback or Log4j is better here. Based on the face that Log4j 2.x is not yet available for Android [see “How to use log4j2 with Android (Logcat)?“], the natively implemented logback-android is the most promising solution for transferring knowledge and making use of familiar logging functions from the Java environment to Android.

We will now take a closer look at logback-android.

logback-android vs. Logback

At its core, logback-android is a fork of Logback in which class dependencies that are not available in the Android SDK have been removed (e.g. JMS, JMX, JNDI and servlets). Instead, logback-android provides additional features such as LogcatAppender and SQLiteAppender [see logback android FAQ]. The current version of logback-android 1.1.1-6 is based on Logback version 1.1.1, as shown by the first three digits of the version number.

License

All components are open source. The SLF4J API is released under a permissive MIT license. logback-android itself inherits the dual licensing model used by Logback using the Eclipse Public License (EPL, also used by JUnit) and the GNU Lesser General Public License (LGPL) to make using it as simple as possible. The LGPL is used to avoid incompatibilities with the GPL. The EPL allows use in areas where the LGPL may impose limitations [see logback-Lizenz].

Hands on

The basic setup in Android is the same as with Logback. The dependencies for the logging API (SLF4J) and for implementation (logback-android in this case) are required; see listing 1.

compile 'org.slf4j:slf4j-api:1.7.21'
apk('com.github.tony19:logback-android-classic:1.1.1-6') {
    // workaround issue #73
    exclude group: 'com.google.android', module: 'android'
  }

Listing 1: build.gradle

It is recommended that logback-android is declared in the apk scope (comparable to the runtime scope in Maven). This means the classes are first available at runtime and not during development. This ensures that you are always programming against the SLF4J API. In some special cases this can be changed; more on that later.

Log statements can now be created in code as usual using the SLF4J API. See listing 2.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class MainActivity {
 private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);

 public void someMethod() {
  LOG.info("SLF4J info");
 }
}

Listing 2: Outputting a log statement

As with Logback, configuration is done in logback.xml which on Android is placed in the assets directory. Listing 3 shows a simple example: Log statements issued by all loggers with a level DEBUG or higher are passed on to the Android system log.

Android saves the following metadata along with the actual log message [see Logcat]:

  • Package name of the app (configured in the AndroidManifest.xml file for the app),
  • Log level, synonymous with priority (set within the call in the code),
  • Tag (can be configured using logback-android),
  • Process ID (PID) and Thread ID (TID) as well as
  • Timestamp (set by the system).

Listing 3 shows the tag being set to the name of the logger (shortened to twelve characters) using tagEncoder. The encoder tag allows you to format the actual message. Logback and logback-android provide options for adding additional information to the message [see logback-Konfiguration]. Since Android already saves a range of metadata there is no need for us to do this ourselves in listing 3 and only the actual message is passed. The line break (%n) required by other appenders is also not necessary here.

<configuration>
  <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
    <tagEncoder>
      <pattern>%logger{12}</pattern>
    </tagEncoder>
    <encoder>
      <pattern>%msg</pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="logcat" />
  </root>
</configuration>

Listing 3: logback.xml

Alternatively, configuration can be done directly within AndroidManifest.xml or in the code (requires the compile scope in build.gradle) [see logback-android-Manifest].

The call shown in listing 2 uses the configuration from listing 3 to create the following example Android log output (copied from Android Studio):

01-22 12:23:33.800 24191-24191/info.schnatterer.logbackandroiddemoI/i.s.l.MainActivity: SLF4J info

This message is formatted as follows:

<Timestamp><PID>-<TID>/<Package Name> <Log Level>/<Tag>: <Message>

This configuration mechanism can even be used for more complex use cases, which we will now take a look at.

Advanced usages

Since Android apps are client applications, the logging requirements are to some extent different to those of a JEE application that runs on a server. For example, you may wish to display logs within the application, send them from the application, change the log level at runtime, or send crash reports in order to help find the cause of errors.

Implementation of these requirements cannot be done out of the box. They can be set up, however, with very little extra effort.

The example application used in this article [see logback-android-Demo]

provides a button that allows logs to be created using SLF4J and JUL; see Figure 2.

Fig. 2: Display of logs within the application

The application shows you how to:

  • configure, in logback.xml, writing to a logfile that is rolled over daily,
  • display log file contents in a TextView,
  • set the log level for the root logger as well as the logcat and file appenders at runtime (via Preference),
  • send all logfiles via email and
  • open the current logfile in an external editor.

The required logic, which is dependent on logback-android, is contained within the straightforward Logs class that was exported into a reusable project [see logback-android-utils].

As this functionality is not provided by the SLF4J API, the dependency scope for logback-android must be changed in build.grade to compile.

At this point it is also important to be aware of the logger hierarchy: The root logger is the parent element of loggers. The root logger will have priority if set to a higher log level than the logcat appender. Example:

  • root log level: WARN,
  • Logcat log level: INFO
  • Result: Logcat will only log statements with a level of WARN or above.

 

The simplest option here is to simply use the same setting for all appenders and control these using the root logger. If different levels are used for the appenders, then it is easiest to set the root logger to ALL and set the log levels for each individual appender. You must otherwise constantly pay attention to ensure that the root logger has the same or a higher level as the appender in order to avoid unexpected behaviour.

Not shown in this example is the connection to a crash reporting provider. However, this is relatively simple to do using the ACRA library. This lets you declaratively attach the logfile to a crash reporter [see ACRA-Logdatei]. The backend to which the crash report is sent can be configured in ACRA. Self-hosted open source solutions (e.g. Acralyzer) and commercial services (e.g. Hockeyapp) are available [see ACRA-Backends].

Finally, one limitation of logback-android should be mentioned: There is currently no option to redirect to SLF4J any log statements that are written directly to the Android log. Log statements from third-party Android libraries and from Android itself using the package name of the app are therefore not under the control of logback-android. This means that they are neither limited by the configured log levels nor forwarded to the appenders.

ProGuard and Lint

If using ProGuard to optimise the APK, then the proguard-rules.pro should be amended according to the instructions in order to have all required classes available at runtime [see ProGuard]. Experience has shown that adding the following statements is usually helpful:

-dontwarn org.slf4j.**

Using Android Lint requires a little more work. The javax.mail dependency acquired from Logback is not available in the Android SDK. There are several solutions to this problem:

  • If not using the SMTPAppender, then you can ignore the logback-android JAR in the corresponding Lint check using lint.xml [see logback-android-Demo lint]. Alternatively, the check can be deactivated completely in build.gradle. The second option here is not advisable, as problems with other dependencies will also be missed.
  • If you are using the SMTPAppender, then you need to add an implementation for javax.mail to the dependencies. A possible option would be android-mail [see logback-android lint Errors].

Alternatives and size

In recent years a range of logging frameworks specially for Android have been developed, e.g. [see Timber] and [see Logger]. These are small, lightweight frameworks that make working with android.util.Log easier. Their lightweight nature is what gives them the advantage over logback-android. They can be more quickly integrated and configured, are just a few kB in size, and have just a few hundred methods. This last point is not insignificant due to the Dalvik Executable (DEX) 65536 method limit. For comparison: logback-android and its dependencies contain approx. 4500 methods (measured using [see dex-method-counts], as the difference between two identical APKs with and without logback-android v1.1.1-6).

The advantages of logback-android lie in its comprehensive range of features and configurability as well as the ability to make use of existing knowledge from Java.

Based on GitHub stars, timber and logger are more well known with several thousand stars compared to fewer than 500 for logback-android (as of 01/2017). To what extent these figures are representative is debatable, however, as Logback and SLF4J, despite being the defacto Java standard for many years, have fewer than 1000 stars.

 

Conclusion

This article is based upon experiences gained from developing an app [see nusic]. logback-android has been successfully used for many years with the features described above and allows standardised configuration of the log level (even for third-party libraries) and logging in files. This allows logs to be displayed within the app and to be sent via email.

Those looking to use the logging features found in Java and make use of their previous Java experience under Android without bumping up against the DEX method limit are well advised to take a look at logback-android. The SLF4J abstraction layer will make it much easier to change the logging framework in the future, if desired.

Pure Android developers with no background in Java may also be interested in the functionality offered by logback-android.

It is in any case sensible to evaluate what alternatives are available before deciding on a particular logging framework. Special attention should be given here to the integration of the logging framework with the crash reporting provider used.

Share this article

Johannes Schnatterer
Solution Architect
With a special focus on quality, open source enthusiasm, a touch of pedantry and Boy Scout Rule under his belt, he is trying to make the world of IT a little better every day.