We all have heard of logs and know how to use them; and we do it pretty often. We love to use logs every now and then, we use logs for api responses, for exceptions, for simple if else debugs; for everything. But, here is the deal: Should we keep doing that? I mean, when we are about to ship our app we have to go over all the project hunting down every log, and damn those logs are hard to find!

Say we have several logs, and we want to get ride of them when we ship our app, how do we do that? Sure we can use our IDE to find all Log.* lines, but eventually we can end up with too many lines. What will we do when hundreds of lines were found? Delete one by one? Comment all of them? And after we ship our app and we want to retrieve all our logs again, what will we do? Will we remove all lines, compile the project and then do a git checkout? Sounds a bit lame, doesn't?

One can say "I've the solution! Let's create our very own log method and check if it's a DEBUG build; if so we log, otherwise we don't!".

Well… It will work, however if we want to log different priority levels, we will have to either create multiple methods or add the priority level as param so we can use the proper log.

As much as we could fabricate a decent solution, we all know that we shouldn't be reinvent the wheel and, as you might know: There’s a simpler — and probably better — solution; meet Timber.

Timber is — with few words — an API for Android’s Log class. It basically enhances the logs from Android. We do that by planting a Tree, and each time we log something, the behavior may change depending on which Tree implementation end up been called.

Download

compile 'com.jakewharton.timber:timber:4.5.1'

How it works

1— Create a Tree instance and plant it ASAP. (ASAP usually means that we should add it to our application class in the onCreate)

2 — Start logging using Timber's calls, rather than Log.

3 — That’s it, we are good to go.

Seriously, it is as difficult as that.

Regardless of how hard it is or not, I’m not writing just those 3 steps. Let’s understand a bit more how Timber works and what we can do with that.

Logging with Timber class

After we setup our Tree(s) we should start logging using Timber class, and it is really simple. It contains the same log methods of the Log class, for example: .v, .d, .i, .w and .e. Since those are similar, if we were developing a project and now we want to use Timber library, we can simple replace all Log. calls for Timber.* calls; but that is not really special, isn’t?

Noticed the Timber calls doesn't possess a tag? That's because Timber will find out which class it is been called and will add that as its tag.

Also, Timber has the .wtf the famous What The F... I mean, What a Terrible Failure. Unlike the ERROR which — as it name states — is an error that may or may not occur eventually, and if so we know how to handle them and work our way out of it. The WTF is normally an error so deep and problematic that there’s no way we can recover from it, usually the WTF should — depending on our app — terminate your app and send an error report or something more important.

Lint rules

Timber posses some Lint rules that are pretty handy. They are divided between two sets: errors and warnings. As you might imagine, the errors render our app unable to compile and warnings do, as it says; shows a warning but allow our app to compile.

If we provide a wrong amount of arguments, a argument with different type while using string interpolation, or tags with size longer than Android’s maximum length, it will trigger an error rule.

If we log using Log class to log, a String.format or concat Strings inside a Timber call or logging an Exception, it will trigger a warning rule.

You can check more about those rules here.

Plant our own Tree

As said in the step 1, we should create an instance of a Tree and plant it but, what is it?

A Tree is basically a class that contains the behavior of our logs. To instantiate it, we must extends a Tree class and implement the log method. Every time we log using Timber, it will go though the Tree we instantiate — if we planted multiple Trees it will go though all of them — and will behavior as we want.

For example, say we want to log nothing when our app is released, what do we do? Well, first we create a Tree that does nothing in the log method.

Then we plant it at the application class inside the onCreate method:

With that, no Timber.* will ever log something, but we want to do that only when we release our app; for that we can simple add a if.

We are good to go! Let’s test it!

If you do test our app, you will notice it will log nothing. What happened? Well, we are using the Timber.* when we have no Tree planted for the debug app! What a shame! As mush as it works when our app is released, while debugging, our app has no Tree. How do we solve that? Do I have to create a Tree only for debugging purposes? Well… kinda of; you do not have to create one, but you have to plant one.

Timber ships with a Tree ready for debug! Meet the DebugTree.

Simple plant it and our app will work flawlessly.

If it’s a debug app, it will use the DebugTree if it’s a release app, it will use NotLoggingTree. Not only we fixed our app, we also get ride of that pesky if not.

But hey, since we aren’t logging with our released app, if something goes wrong; what will we do? How about we send our errors and warnings to our crash library? Sounds great, isn’t?

Well that’s really easy! Just grab your Custom Tree and add a behavior so when you log an error or warning it calls your crash library!

That’s it. Errors and warnings will be sent to our crash library when the app is released, and all other log levels will be ignored. We no longer need to keep searching and removing — or commenting — logs lines when we release our app.

But you know what? I do not like that if (BuildConfig.DEBUG). You see, it is a kind of if production and that sounds weird, luckily we can solve that — and probably many other things — just by creating classes for different build variants. Do not know how to do that? Don’t worry, I’ve got you covered.

Using build variants

First thing, let's create a class to handle the Timber implementation, so we can remove the logic from our Application class.

However, we yet have this if else I said we would get off it, for that we must create twice the TimberLogImplementation class within the same package. You heard it right: Twice.

In order to do that, we must create two directories inside the src: The release and the debug. Inside this directory, we simple create another directory called java and create the packages normally, as we do in the main directory. Using the Project view, our directory should look something like this:

Once you created that, depending on your build variant the IDE will compile one of the directories or the other.

After we've created this, we refactor our TimberLogImplementation class, the release class should have only the release implementation.

Whereas our debug class will have — you guessed — only the debug implementation.

Done! Now when you change the build variant to debug it will compile the debug/java/.../TimberLogImplementation and when you change to release it will compile the release/java/.../TimberLogImplementation . No more pesky if production, all great! But wait, there is more! I have a last bonus!

Say we want to have more information in our debug, for instance the class and the line of that log. Usually we would simple add those at the message, like Timber.i("MyClass:13-Something happen here!") but there is a issue — actually many, but let's keep it simple — if we add more code, the line of the log might not be the same, we can between some copy&paste add the wrong class, and stuff like that. Lucky we can simple do the following.

We are intercepting the log of our DebugTree adding at the beginning, the class name and the line number!

If you want a project with Timber, you can checkout this project I've added to my Github.

I hope you have enjoyed this small article, I will be back soon with more stuff. From now on, I hope you always use Timber in your projects!

Stay awesome :)