Timber — what it is, library API and usage examples

Author: IT Sectr Published: 2026-05-28 Reading time: 8 min

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 — a wrapper over android.util.Log with automatic tag detection by class name and call stack
  • Tree — the basic element of Timber architecture, each instance defines how to process a log message
  • Planting trees — the process of registering a Tree in Timber, usually done once in Application.onCreate
  • DebugTree — a built-in implementation for Debug builds, outputs logs to Logcat with the class name as tag
  • Custom Tree — the ability to create your own implementation for sending logs to Crashlytics, a file, or a server

What is Timber

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.

Timber Architecture: Trees and Forest

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 — Built-in Implementation for Development

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.

How the Forest Works

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.

Thread Safety

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.

kotlin
// 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")
    }
}

Installing and Setting Up Timber in an Android Project

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.

groovy
// 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.

Creating a Custom Tree for Custom Log Handling

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.

kotlin
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.

Timber vs Standard android.util.Log

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.

Parameterandroid.util.LogTimber
Tag detectionManual, string constantAutomatic, via call stack
FormattingConcatenation or String.formatBuilt-in varargs + %s placeholder
Output channelsLogcat onlyTrees: Logcat, file, Crashlytics, etc.
Behavior without initializationAlways worksOutputs nothing
PerformanceBaseline levelLazy 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.

Best Practices When Using Timber

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

Can Timber be used in an Android library module?

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.

How does Timber determine the tag without manual specification?

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.

How is Timber different from Logcat?

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.

Does Timber support Kotlin Multiplatform?

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.

How to remove all planted trees in Timber?

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

  • Timber — a lightweight wrapper over android.util.Log with automatic tag and tree architecture
  • Tree — the basic element, each tree defines its own log output channel
  • DebugTree — built-in implementation for Logcat, automatically disabled in release
  • Custom Tree — sends logs to Crashlytics, files, server, or any other channel
  • Timber.tag() — temporary tag change for library code without global configuration
  • Thread safety — all Timber methods are synchronized, custom trees require their own synchronization
  • Safe silence — when no trees are present, Timber does not throw exceptions and does not consume resources

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.

Discuss the project

Read also