Stripping Logging From Your App, part 3

Clive Lee
3 min readDec 19, 2017

In part 1 of the series, we went over why you shouldn’t use Proguard to strip logging. TLDR: “[I]t’s relying on a setting specified in a file you don’t usually see (your custom .pro file) that is affected by a setting in other files you see even less (default Proguard files).” In part 2, we went over Aspect-Oriented Programming as it applies to logging, and why it’s not quite what we’re looking for. In this part 3 of the series, we’ll be looking at using dependency injection along with a wrapper class. I briefly suggested this at a conference, but I didn’t get a chance to flesh out the advantages and disadvantages of following this approach.

Let’s summarize what we’re looking for. We want to be able to (1) strip logging from our code for release builds, (2) have some flexibility in how we strip the logging (i.e. through excluding source files from the build, or via runtime statements), and (3) log from pure java or pure kotlin gradle modules.

The dependency injection / wrapper approach can be summarized like this. In your pure java module, you create a ‘logger’ interface that you implement in an android module. Any time you want to log in a pure java/kotlin module, you inject an instance of an implementation of the interface, which will log to Android’s logcat.

Wrap it good!

In the figure above, core and domain are java libraries, while app and android-core are android modules. Inside core, the LogStrategy interface (or should that be a Facade? Depends on how you use it!) exposes various ways in which you can print log statements. For instance, it may have a d(String Tag, String msg) method to log debug-level logging messages. This interface may be implemented by the TimberWrapper class which calls the corresponding Timber methods, as well as performing any initializations. In a pure java module, such as the domain module (or the aforementioned core module), we can inject the dependency to any java classes that needs logging. For instance, the UseCase class may be injected with a LogStrategy interface. In the android module that class the pure java class, we can then inject an instance of the android-specific implemented.

This approach seems to have everything we’re looking for. We can strip logging from our release builds, either via runtime BuildConfig.DEBUG check or, say, creating an empty debug/TimberWrapper class. And we can log from our pure java/kotlin modules, like in the domain module above.

But there is one HUGE downside to this approach. If you’ve read the part 2 of this series, you may remember the advantage of annotation-based logging like Hugo. Namely, there was a cleaner separation between the logging class and the method that was using the logging class. But in the dependency injection approach, we’re literally adding a dependency to a class that really shouldn’t depend on logging. Why should, for example, a UseCase class depend on or need to know about logging in order to perform its one, single responsibility?

Another disadvantage is that this approach opens up the possibility for misuse by the consumer of the injected class. For instance, if you are providing the domain module as a library, the user of the library may create multiple instances of a logging wrapper class instead of reusing the same one. (As a quick aside, you may counter by saying that you’re not writing your modules like a library. To which I would reply that, doing so actually makes your code a lot easier to maintain, by reducing what you need to remember to reuse your code.)

Thus, although this dependency injection / wrapper approach holds some promise, it would seem as though there are at least couple of disadvantages. And thus our quest remains to find an adequate way to log and strip logging from our code.

--

--