or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/maven-com-jakewharton-timber--timber

A logger with a small, extensible API which provides utility on top of Android's normal Log class

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/com.jakewharton.timber/timber@5.0.x

To install, run

npx @tessl/cli install tessl/maven-com-jakewharton-timber--timber@5.0.0

index.mddocs/

Timber

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

Package Information

  • Package Name: com.jakewharton.timber:timber
  • Package Type: Android Library (Gradle/Maven)
  • Language: Kotlin
  • Installation: Add implementation 'com.jakewharton.timber:timber:5.0.1' to your build.gradle

Core Imports

import timber.log.Timber

Basic Usage

import 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)

Architecture

Timber is built around several key components:

  • Timber Class: Main static API (Forest companion object) that delegates to planted Tree instances
  • Tree Abstract Class: Base class for logging implementations with all logging methods
  • DebugTree: Concrete implementation that automatically infers tags from calling class
  • Tree Management: Plant/uproot system for adding and removing logging behavior
  • Thread-Local Tagging: Support for one-time custom tags per logging call

Capabilities

Static Logging Methods

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")

Tree Management

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: Int

Usage 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.treeCount

Tag Management

Support 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): Tree

Usage 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")

Utility Methods

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(): Tree

Usage 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")
    }
}

Tree Abstract Class

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())

DebugTree Implementation

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())

Constants and Priority Levels

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 = 7

Error Handling

Timber handles various error conditions gracefully:

  • Null messages: If message is null and no throwable, the log call is swallowed
  • Empty messages: If message is empty, throwable stack trace is used as message
  • Unplanted trees: Calling uproot() on a non-planted tree throws IllegalArgumentException
  • Self-planting: Attempting to plant Timber into itself throws IllegalArgumentException
  • Long messages: DebugTree automatically chunks messages longer than 4000 characters
  • Thread safety: Tree management operations are synchronized for thread safety

Common Patterns

Application Setup

import 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())
        }
    }
}

Activity Logging

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
    }
}

Network Logging

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)
        }
    }
}