Logging to files in Android — The Reactive way

Apr 26, 2020 • 3 min read

As Android developers, we always wanted to know how our apps are behaving with the users.

...

I assume you are familiar with Timber and RxJava. Timber is widely used by many and has more than 8k stars in Github. With the power of RxJava, we can do more than just printing logs.

Let’s just get started with the code.

public class FileLoggingTree extends Timber.DebugTree {
   ....
}

Yes, we’ll be using the DebugTree. By default, the DebugTree creates tags automatically, from the class Timber.xx() is being used. This is not possible with a normal Tree. Also we don’t want to implement the auto-tag-creation ourself, so let’s just stick to this tree.

Next we create a file with current date as the filename. This means that, a new file is created everyday.

FILE_DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy");
....
String filename = FILE_DATE_FORMAT.format(new Date());
FileUtil.getAsFile("logs", "logs_" + filename + ".log");

Log rotations in the file can be implemented to avoid creating new files.

Subjects in RxJava can act both as an observable and an observer. So we can both emit and subscribe to log messages. It seems like perfect for our requirement, but there is also something called RxRelay.

Relays are similar to Subjects. The only difference is there is no onComplete() event in Relays. In Subject when onComplete() is called, it accepts no more events. Since we need to keep writing logs to file, and don’t want to accidentally complete the observable, Relays are perfect.

We initialize the ReplayRelay that write logs to the file every 5sec or a maximum of 10 log count is reached (which ever is first). Just customize these values as per your requirement.

...
private Relay<String> logBuffer = ReplayRelay.<String>create().toSerialized();
public FileLoggingTree() {
    logBuffer.buffer(5,TimeUnit.SECONDS,10)
             .observeOn(Schedulers.io())
             .subscribe(this::_writeToFile);
}
...

Note

The Relay must be serialized to make it thread-safe. I almost had them un-serialized in one of my production app and wasn't able to figure out the error easily.

And then create a format for the log messages,

LOG_DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss.SSSaa");
...
@Override
protected void log(...) {
    super.log(...); // Optional to also print in the debug console
    String dateTime = LOG_DATE_FORMAT.format(new Date());
    String logType = _getLogType(priority);
    String logMessage;
    if (throwable == null) {
        logMessage = String.format("%s %s%s: %s\n", dateTime, logType, tag, message);
    } else {
        logMessage = String.format("%s %s%s: %s\n%s\n", dateTime, logType, tag, message, throwable);
    }
    logBuffer.accept(logMessage);
}Î

That’s it. Just plant this tree in the App’s onCreate() method.

...

The complete code available at my Github gist (opens new window)


Abarajithan
Last updated: 9/9/2023, 8:28:10 AM