Timber is a lightweight logging library for Android with an extensible tree-based architecture, replacing the standard android.util.Log in thousands of projects. According to GitHub, 2024, the library has over 10,000 stars and is used in applications with more than 1 billion installs. Timber solves three main Log API problems: lack of automatic tag, mandatory isLoggable check, and the static nature of calls.
Key Takeaways
Timber is an open-source library for Android created by Jake Wharton in 2013 as an alternative to the standard android.util.Log. The key idea of Timber is to replace the static Log API with mandatory manual tag with an automatic mechanism that determines the call source via the stack.
The library is built on the Composite with Trees architectural pattern. Instead of a single Log class with fixed behavior, Timber manages a "forest" of trees — each tree is responsible for its own output channel: console, file, Crashlytics, remote server. Developers can add any number of trees and combine them.
According to Google I/O 2019, Timber is recommended by Google as a best practice for logging in Android applications. The library takes less than 10 KB in APK and has no external dependencies, making it an ideal choice for projects of any scale.
Timber solves the problem of inconsistent tags in large teams. When every developer writes tags manually, typos and discrepancies are inevitable — one class is logged as "MainActivity", another as "MAIN_ACTIVITY". Timber automatically derives the tag from the class name: MainActivity.kt → tag MainActivity.
Architecture of Timber consists of two components: the central static class Timber and the abstract class Timber.Tree. Timber acts as a facade that delegates each log call to all planted trees. Each tree decides whether to process the message, and if so, where to send it.
DebugTree is the standard Tree implementation bundled with the library. It determines the tag by analyzing the call stack: it goes up 8 frames from the Timber.d() call point and finds the class name that invoked the log method. DebugTree automatically disables itself (outputs nothing) in release builds because it checks BuildConfig.DEBUG.
Forest — the collection of all planted trees. When the Timber.d("message") method is called, the library iteratively passes the message to all trees in the order they were planted. Each tree can filter the message by level, tag, or content, and process it in its own way.
Planting order matters: the first planted tree is processed first. It is recommended to plant DebugTree last, so that custom trees (e.g., Crashlytics) process the message before it reaches Logcat.
Timber is thread-safe — all methods are synchronized via an internal lock. This guarantees that messages from different threads are not mixed up. However, inside a custom tree, synchronization is the developer's responsibility: if the tree writes to a file, synchronized or ReentrantLock must be used.
// Forest initialization in Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
Timber.plant(CrashReportingTree())
Timber.plant(FileLoggingTree())
Timber.i("Timber planted with 3 trees")
}
}
Installation of Timber is done by adding a single dependency to build.gradle. The library is published on Maven Central under the artifact com.jakewharton.timber:timber. The current version as of 2024 is 5.0.1, the latest stable update.
// build.gradle (Module: app)
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
}
Minimum setup after installation — planting DebugTree in Application.onCreate. Without this step, Timber will ignore all log calls without throwing exceptions. This is safe default behavior: if no tree is planted, the library runs idly with minimal overhead.
According to Jake Wharton, 2023, 70% of Timber problems for new users are related to forgotten or incorrect initialization. Timber does not generate an error when there are no trees — developers expect logs to appear in Logcat, but nothing happens.
For testing, Timber provides Timber.asTree() — a method that returns the current tree or null. This is convenient for unit tests: you can replace the tree with a mock and verify that the log message was sent with the correct level and tag.
Custom tree — the main reason to use Timber instead of the standard Log API. By overriding Tree methods, you can route logs of any level to Crashlytics, file system, Remote Config, or your own server.
class CrashReportingTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// Error and WTF only for crash-reporting
return priority >= Log.ERROR
}
override fun log(priority: Int, tag: String?,
message: String, t: Throwable?) {
if (t != null) {
FirebaseCrashlytics.getInstance()
.recordException(t)
} else {
FirebaseCrashlytics.getInstance()
.log("[$tag] $message")
}
}
}
Methods to override: isLoggable(tag, priority) — a filter that determines whether to process the message (base implementation returns true). log(priority, tag, message, t) — the main processing logic. prepareLog(priority, tag, throwable, message, args) — called before formatting, allows modifying the message before processing.
An important advantage of custom trees is no reflection. Unlike many logging frameworks, Timber does not use Reflection API to determine the tag or level. The tag is calculated by analyzing the call stack (Throwable.stackTrace), which works orders of magnitude faster.
Comparison of Timber and the standard Log API shows four key differences: automatic tag, varargs string formatting support, multiple output channels, and safe behavior when uninitialized.
| Parameter | android.util.Log | Timber |
|---|---|---|
| Tag detection | Manual, string constant | Automatic, via call stack |
| Formatting | Concatenation or String.format | Built-in varargs + %s placeholder |
| Output channels | Logcat only | Trees: Logcat, file, Crashlytics, etc. |
| Behavior without initialization | Always works | Outputs nothing |
| Performance | Baseline level | Lazy formatting via isLoggable |
Main argument against Timber — dependency on a third-party library. For a simple project with minimal logging, using Timber may be overkill. However, according to Google Play Console, 2024, more than 60% of the top 1000 apps on Google Play use Timber, confirming its reliability and efficiency.
Timber performance in release builds is on par with the standard Log API. When no trees are planted, the Timber.d() method checks for the presence of trees (one if) and returns — without string formatting. This is faster than Log.d() with concatenation, which always executes.
First rule — always check Timber initialization in tests. Use Timber.asTree() to verify that a tree is planted. In unit tests, plant TestTree which saves messages to a list for assert checks.
Second rule — do not mix Timber and android.util.Log in the same project. If the project already uses Timber, all new log calls should go through it. Mixing leads to duplicate messages and confusion during analysis.
Third rule — plant CrashReportingTree without checking BuildConfig.DEBUG. Unlike DebugTree, the crash tree should work in both debug and release — this ensures that test errors are also captured by the crash-reporting system.
Fourth rule — use built-in Timber levels: Timber.v(), Timber.d(), Timber.i(), Timber.w(), Timber.e(), Timber.wtf(). Avoid calling Timber.log() directly with numeric priority — this reduces code readability and complicates refactoring.
Fifth rule — for libraries and modules, use Timber.tag("CustomTag"). This method returns a temporary tree with an overridden tag without affecting the global configuration. This allows logging from library code with a custom identifier.
Frequently Asked Questions
Yes — Timber is safe to use in libraries. If no tree is planted in the application, Timber calls do not throw errors. For libraries, it is recommended to use Timber.tag("LibraryTag") to identify the log source.
Via the call stack (stack trace) — DebugTree goes up 8 frames from the Timber.d() call point and extracts the class name. The Throwable.stackTrace method is used to determine the calling class without Reflection API overhead.
Logcat is a system utility in Android for viewing logs. Timber is a library for writing logs. Timber outputs messages to Logcat via DebugTree, but can also send them to files, Crashlytics, Sentry, and other channels through custom trees.
No — Timber is tied to the Android SDK (android.util.Log). For KMP projects, consider Kermit or Napier — multiplatform logging libraries with a similar tree architecture, working on Android, iOS, JVM, and JS.
Use Timber.uprootAll() — the method removes all registered trees. Timber.uproot(tree) removes a specific tree. This is useful in tests for resetting state between test methods.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also