A logger with a small, extensible API which provides utility on top of Android's normal Log class
npx @tessl/cli install tessl/maven-com-jakewharton-timber--timber@5.0.0Timber is a logger with a small, extensible API which provides utility on top of Android's normal Log class. It uses a Tree-based architecture where logging behavior is added through Tree instances that can be planted into the Timber system. The library automatically determines the calling class for log tags and supports string formatting with proper exception handling.
implementation 'com.jakewharton.timber:timber:5.0.1' to your build.gradleimport timber.log.Timberimport timber.log.Timber
// In your Application class onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
// Throughout your app
Timber.d("Debug message")
Timber.i("Info message with %s", "formatting")
Timber.w(exception, "Warning with exception")
Timber.e(exception)Timber is built around several key components:
All logging methods are available as static calls that delegate to all planted Tree instances.
// Verbose logging
fun v(message: String?, vararg args: Any?)
fun v(t: Throwable?, message: String?, vararg args: Any?)
fun v(t: Throwable?)
// Debug logging
fun d(message: String?, vararg args: Any?)
fun d(t: Throwable?, message: String?, vararg args: Any?)
fun d(t: Throwable?)
// Info logging
fun i(message: String?, vararg args: Any?)
fun i(t: Throwable?, message: String?, vararg args: Any?)
fun i(t: Throwable?)
// Warning logging
fun w(message: String?, vararg args: Any?)
fun w(t: Throwable?, message: String?, vararg args: Any?)
fun w(t: Throwable?)
// Error logging
fun e(message: String?, vararg args: Any?)
fun e(t: Throwable?, message: String?, vararg args: Any?)
fun e(t: Throwable?)
// Assert logging
fun wtf(message: String?, vararg args: Any?)
fun wtf(t: Throwable?, message: String?, vararg args: Any?)
fun wtf(t: Throwable?)
// Generic priority logging
fun log(priority: Int, message: String?, vararg args: Any?)
fun log(priority: Int, t: Throwable?, message: String?, vararg args: Any?)
fun log(priority: Int, t: Throwable?)Usage Examples:
import timber.log.Timber
// Basic logging with string formatting
Timber.d("User %s logged in with ID %d", username, userId)
// Exception logging
try {
riskyOperation()
} catch (e: Exception) {
Timber.e(e, "Failed to perform risky operation")
// Or just log the exception
Timber.e(e)
}
// Different log levels
Timber.v("Verbose details")
Timber.i("Information message")
Timber.w("Warning message")
Timber.wtf("What a Terrible Failure!")
// Custom priority levels
Timber.log(Log.DEBUG, "Custom priority debug message")Methods for adding and removing logging implementations.
/**
* Add a new logging tree
* @param tree Tree instance to add to the forest
*/
fun plant(tree: Tree)
/**
* Add multiple logging trees
* @param trees Vararg array of Tree instances to add
*/
fun plant(vararg trees: Tree)
/**
* Remove a planted tree
* @param tree Tree instance to remove from the forest
*/
fun uproot(tree: Tree)
/**
* Remove all planted trees
*/
fun uprootAll()
/**
* Return a copy of all planted trees
* @return Unmodifiable list of currently planted trees
*/
fun forest(): List<Tree>
/**
* Get the number of currently planted trees
*/
val treeCount: IntUsage Examples:
import timber.log.Timber
// Plant trees for different environments
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
} else {
// Plant custom production trees
Timber.plant(CrashlyticsTree(), FileLoggingTree())
}
// Remove specific tree
val debugTree = Timber.DebugTree()
Timber.plant(debugTree)
// Later...
Timber.uproot(debugTree)
// Clear all trees
Timber.uprootAll()
// Check planted trees
val currentTrees = Timber.forest()
val treeCount = Timber.treeCountSupport for one-time custom tags for specific logging calls.
/**
* Set a one-time tag for use on the next logging call
* @param tag Custom tag to use for the next log message
* @return Tree instance for method chaining
*/
fun tag(tag: String): TreeUsage Examples:
import timber.log.Timber
// Use custom tag for specific log message
Timber.tag("CUSTOM_TAG").d("Debug message with custom tag")
// Chain with logging call
Timber.tag("NETWORK").i("API request completed")Additional utility methods for accessing Timber as a Tree instance.
/**
* Get Timber's Forest as a Tree instance for dependency injection
* @return Tree instance representing all planted trees
*/
fun asTree(): TreeUsage Examples:
import timber.log.Timber
// Inject Timber as a Tree dependency
class ApiClient(private val logger: Tree = Timber.asTree()) {
fun makeRequest() {
logger.d("Making API request")
}
}Base class for implementing custom logging behavior.
abstract class Tree {
// All logging methods (same signatures as static methods)
open fun v(message: String?, vararg args: Any?)
open fun v(t: Throwable?, message: String?, vararg args: Any?)
open fun v(t: Throwable?)
open fun d(message: String?, vararg args: Any?)
open fun d(t: Throwable?, message: String?, vararg args: Any?)
open fun d(t: Throwable?)
open fun i(message: String?, vararg args: Any?)
open fun i(t: Throwable?, message: String?, vararg args: Any?)
open fun i(t: Throwable?)
open fun w(message: String?, vararg args: Any?)
open fun w(t: Throwable?, message: String?, vararg args: Any?)
open fun w(t: Throwable?)
open fun e(message: String?, vararg args: Any?)
open fun e(t: Throwable?, message: String?, vararg args: Any?)
open fun e(t: Throwable?)
open fun wtf(message: String?, vararg args: Any?)
open fun wtf(t: Throwable?, message: String?, vararg args: Any?)
open fun wtf(t: Throwable?)
open fun log(priority: Int, message: String?, vararg args: Any?)
open fun log(priority: Int, t: Throwable?, message: String?, vararg args: Any?)
open fun log(priority: Int, t: Throwable?)
// Protected methods for customization
@Deprecated("Use isLoggable(String, int)", ReplaceWith("this.isLoggable(null, priority)"))
protected open fun isLoggable(priority: Int): Boolean
protected open fun isLoggable(tag: String?, priority: Int): Boolean
protected open fun formatMessage(message: String, args: Array<out Any?>): String
// Abstract method that must be implemented
protected abstract fun log(priority: Int, tag: String?, message: String, t: Throwable?)
// Internal properties
internal val explicitTag: ThreadLocal<String>
internal open val tag: String?
}Custom Tree Implementation Example:
import timber.log.Timber
import android.util.Log
class CustomTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// Only log warnings and errors in production
return priority >= Log.WARN
}
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// Send to custom logging service
MyLoggingService.log(priority, tag, message, t)
}
}
// Usage
Timber.plant(CustomTree())Concrete Tree implementation for debug builds that automatically infers tags.
open class DebugTree : Tree() {
/**
* Extract the tag from a stack trace element
* @param element Stack trace element to extract tag from
* @return Extracted tag name, may be null
*/
protected open fun createStackElementTag(element: StackTraceElement): String?
/**
* Log implementation that handles long messages and uses Android Log
* @param priority Log priority level
* @param tag Log tag (may be null)
* @param message Formatted log message
* @param t Optional throwable
*/
override fun log(priority: Int, tag: String?, message: String, t: Throwable?)
companion object {
private const val MAX_LOG_LENGTH = 4000
private const val MAX_TAG_LENGTH = 23
}
}DebugTree Customization Example:
import timber.log.Timber
class CustomDebugTree : Timber.DebugTree() {
override fun createStackElementTag(element: StackTraceElement): String? {
// Include line number in tag
return "${super.createStackElementTag(element)}:${element.lineNumber}"
}
}
// Usage
Timber.plant(CustomDebugTree())Timber uses Android Log priority constants:
// Android Log priority constants (from android.util.Log)
const val VERBOSE = 2
const val DEBUG = 3
const val INFO = 4
const val WARN = 5
const val ERROR = 6
const val ASSERT = 7Timber handles various error conditions gracefully:
uproot() on a non-planted tree throws IllegalArgumentExceptionIllegalArgumentExceptionimport timber.log.Timber
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
} else {
// Plant production trees
Timber.plant(CrashlyticsTree(), FileLoggingTree())
}
}
}import timber.log.Timber
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.d("MainActivity created")
try {
setupViews()
} catch (e: Exception) {
Timber.e(e, "Failed to setup views")
}
}
private fun setupViews() {
Timber.v("Setting up views")
// Implementation
}
}import timber.log.Timber
class ApiClient {
fun makeRequest(url: String) {
Timber.tag("NETWORK").d("Making request to %s", url)
try {
val response = httpClient.get(url)
Timber.tag("NETWORK").i("Request successful: %d", response.code)
} catch (e: IOException) {
Timber.tag("NETWORK").e(e, "Request failed for %s", url)
}
}
}