MeasureMe was my first native Android application. previously i’ve commented on the basic framework needed to start developing an app and one of the most basic elements is logging.

in my previous projects in .net i have used emterprise library and log4net and they have both been fine. obviously for an android application i was going to need a java based framework. i have already blogged about how i migrated from one logging Enterprise Library to Log4Net. It was relatively easy as I had implement a facade layer so logging framework specific references were in the main body of the code.

When I looked around for an equivalent in Java I found that slf4j is a purpose built logging facade and for Android Logback appeared to be a popular choice. slf4j acts as a barrier meaning that I can swap real real logging implementations behind it.

To get the whole thing off the ground all I needed to do was download slf4j-api-1.7.5.jar and logback-android-1.0.10-2.jar and place them in the libs folder.

Then to perform any logging in a class I just reference an ILoggerFactory like this

public class OpenSourceLicensesActivity extends RoboActivity {

 @Inject
 private ILoggerFactory logger;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_open_source_licenses);

  logger.getCurrentApplicationLogger().debug("OSLicensesActivity.onCreate()");

The ILoggerFactory dependency is injected by Guice as I covered in an earlier post. The implementation of ILoggerFactory for slf4j looks like this

public class SlfLoggerFactory implements ILoggerFactory {
  @Override
  public Logger getCurrentApplicationLogger() {
    return LoggerFactory.getLogger(MeasureMe.class);
  }
}

The actual logging is done by Logback. Logback takes its configuration from an XML file in the assets folder.

<property name="LOG_HOME" value="/sdcard/MeasureMe" />
  <!-- Create a logcat appender -->
  <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
    <encoder>
      <pattern>%msg</pattern>
    </encoder>
  </appender>

  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_HOME}/mm_main.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
      <fileNamePattern>${LOG_HOME}/mm_main.%i.log</fileNamePattern>
      <minIndex>1</minIndex>
      <maxIndex>2</maxIndex>
    </rollingPolicy>

    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>500KB</maxFileSize>
    </triggeringPolicy>
    <encoder>
      <pattern>%date{yyyy-MMM-dd HH:mm:ss.SSS} %t %r %logger{15} - %msg%n</pattern>
    </encoder>
  </appender>

  <logger 
    name="main" 
    level="WARN" 
  >
    <appender-ref ref="logcat" />
    <appender-ref ref="FILE" />
  </logger>

  <root level="WARN">
    <appender-ref ref="logcat" />
    <appender-ref ref="FILE" />
  </root>
  
</configuration>

A point to note here is configuring the path to the logfile, this is set in the property LOG_HOME to be /sdcard/MeasureMe. Not all Android devices have sdcard slots, though even the ones that do not appear to mount emulated sdcards using the name sdcard. However I could find the path programmatically but the only way to do this from code was to break the facade and reach into the logback implementation and set the path, I was keen to keep the code clean so I am still looking for a configuration based solution.

Another interesting part is to configure release and debug builds to have different logging levels I will cover the mechanism in a different post.

The only place in the code where I did not use this pattern is in the main application.

public class MeasureMe extends Application {
	
 // Deliberately not using IoC for this - as this class is where IoC is setup
 final private Logger _logger = LoggerFactory.getLogger(MeasureMe.class);

 @Override
 public void onCreate() {
  _logger.warn("Application started v{}, debug {}, production build {}",
    getVersionName(getApplicationContext()),
    BuildConfig.DEBUG,
    MeasureMeBuildConfig.PRODUCTION);

  super.onCreate();
  RoboGuice.setBaseApplicationInjector(
    this,
    RoboGuice.DEFAULT_STAGE,
    RoboGuice.newDefaultRoboModule(this),
    new IoCModule());

  _logger.debug("Application IoC bound");
}

I wanted to be able to log if I had a bug in the IoC container logic and for that reason the code in the main application gets its own logger in case IoC is broken.