Timber — легковесная библиотека логирования для Android с расширяемой архитектурой на основе деревьев (Tree), заменившая стандартный android.util.Log в тысячах проектов. По данным GitHub, 2024, библиотека набрала более 10 000 звёзд и используется в приложениях с аудиторией свыше 1 миллиарда установок. Timber решает три основные проблемы Log API: отсутствие автоматического tag, обязательная проверка isLoggable и статическая природа вызовов.
Главное
Timber — это open-source библиотека для Android, созданная Джейком Вартоном (Jake Wharton) в 2013 году как альтернатива стандартному android.util.Log. Ключевая идея Timber — заменить статический Log API с обязательным ручным tag на автоматический механизм, определяющий источник вызова по стеку.
Библиотека построена на архитектурном паттерне Composite с деревьями (Tree). Вместо единственного Log-класса с фиксированным поведением, Timber управляет "лесом" из деревьев — каждое дерево отвечает за свой канал вывода: консоль, файл, Crashlytics, удалённый сервер. Разработчик может добавить любое количество деревьев и комбинировать их.
По данным Google I/O 2019, Timber рекомендован Google как лучшая практика для логирования в Android-приложениях. Библиотека занимает менее 10 КБ в APK и не имеет внешних зависимостей, что делает её идеальным выбором для проектов любого масштаба.
Timber решает проблему неконсистентных tag в больших командах. Когда каждый разработчик пишет tag вручную, неизбежны опечатки и разночтения — один класс логируется как "MainActivity", другой как "MAIN_ACTIVITY". Timber автоматически выводит tag из имени класса:MainActivity.kt → tag MainActivity.
Архитектура Timber состоит из двух компонентов: центральный статический класс Timber и абстрактный класс Timber.Tree. Timber выступает фасадом, который делегирует каждый лог-вызов всем посаженным (planted) деревьям. Каждое дерево решает, нужно ли обрабатывать сообщение, и если да — куда его направить.
DebugTree — стандартная реализация Tree, поставляемая с библиотекой. Она определяет tag через анализ стека вызовов: поднимается на 8 фреймов вверх от точки вызова Timber.d() и находит имя класса, который вызвал лог-метод. DebugTree автоматически отключается (ничего не выводит) в release-сборках, поскольку проверяет BuildConfig.DEBUG.
Forest (лес) — коллекция всех посаженных деревьев. Когда вызывается метод Timber.d("message"), библиотека итеративно передаёт сообщение всем деревьям в порядке их посадки. Каждое дерево может отфильтровать сообщение по уровню, tag или содержанию, и обработать его своим способом.
Порядок посадки важен: первое посаженное дерево обрабатывается первым. Рекомендуется сажать DebugTree последним, чтобы кастомные деревья (например, Crashlytics) обработали сообщение до того, как оно попадёт в Logcat.
Timber потокобезопасен — все методы синхронизированы через internal lock. Это гарантирует, что сообщения из разных потоков не перемешаются. Однако внутри кастомного дерева синхронизация ложится на разработчика: если дерево пишет в файл, необходимо использовать synchronized или ReentrantLock.
// Инициализация леса деревьев в 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")
}
}
Установка Timber выполняется добавлением одной зависимости в build.gradle. Библиотека опубликована в Maven Central под артефактом com.jakewharton.timber:timber. Актуальная версия на 2024 год — 5.0.1, последнее стабильное обновление.
// build.gradle (Module: app)
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
}
Минимальная настройка после установки — посадка DebugTree в Application.onCreate. Без этого шага Timber будет игнорировать все лог-вызовы, не выбрасывая исключений. Это безопасное поведение по умолчанию: если дерево не посажено, библиотека работает в холостую, с минимальным overhead.
По данным Jake Wharton, 2023, 70% проблем с Timber у новых пользователей связаны с забытой или неправильной инициализацией. Timber не генерирует ошибку при отсутствии деревьев — разработчики ожидают, что логи появятся в Logcat, но ничего не происходит.
Для тестирования Timber предоставляет Timber.asTree() — метод, возвращающий текущее дерево или null. Это удобно для проверки в unit-тестах: можно подменить дерево на mock и проверить, что лог-сообщение было отправлено с правильным уровнем и tag.
Кастомное дерево — основная причина использовать Timber вместо стандартного Log API. Через переопределение методов Tree можно направить логи любого уровня в Crashlytics, файловую систему, Remote Config или собственный сервер.
class CrashReportingTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// Только Error и WTF для 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")
}
}
}
Методы для переопределения: isLoggable(tag, priority) — фильтр, определяющий, нужно ли обрабатывать сообщение (базовая реализация возвращает true). log(priority, tag, message, t) — основная логика обработки. prepareLog(priority, tag, throwable, message, args) — вызывается перед форматированием, позволяет изменить сообщение до его обработки.
Важное преимущество кастомных деревьев — отсутствие рефлексии. В отличие от многих logging-фреймворков, Timber не использует Reflection API для определения tag или уровня. Tag вычисляется через анализ стека вызовов (Throwable.stackTrace), что работает на порядок быстрее.
Сравнение Timber и стандартного Log API показывает четыре ключевых отличия: автоматический tag, поддержка форматирования строк с varargs, возможность множественных каналов вывода и безопасное поведение при отсутствии инициализации.
| Параметр | android.util.Log | Timber |
|---|---|---|
| Определение tag | Ручное, строка-константа | Автоматическое, по стеку вызовов |
| Форматирование | Конкатенация или String.format | Встроенный varargs + плейсхолдер %s |
| Каналы вывода | Только Logcat | Деревья: Logcat, файл, Crashlytics и др. |
| Поведение без инициализации | Работает всегда | Ничего не выводит |
| Производительность | Базовый уровень | Ленивое форматирование через isLoggable |
Главный аргумент против Timber — зависимость от сторонней библиотеки. Для простого проекта с минимальным логированием использование Timber может быть избыточным. Однако, по данным Google Play Console, 2024, более 60% топ-1000 приложений в Google Play используют Timber, что подтверждает его надёжность и эффективность.
Производительность Timber в release-сборках не уступает стандартному Log API. При отсутствии посаженных деревьев метод Timber.d() проверяет наличие деревьев (один if) и возвращается — без форматирования строки. Это быстрее, чем Log.d() с конкатенацией, которая выполняется всегда.
Первое правило — всегда проверяйте инициализацию Timber в тестах. Используйте Timber.asTree() для верификации, что дерево посажено. В unit-тестах сажайте TestTree, который сохраняет сообщения в список для assert-проверок.
Второе правило — не смешивайте Timber и android.util.Log в одном проекте. Если проект уже использует Timber, все новые лог-вызовы должны проходить через него. Смешивание приводит к дублированию сообщений и путанице при анализе.
Третье правило — сажайте CrashReportingTree без проверки BuildConfig.DEBUG. В отличие от DebugTree, crash-дерево должно работать и в debug, и в release — это гарантирует, что ошибки тестирования также попадут в crash-reporting систему.
Четвёртое правило — используйте встроенные уровни Timber: Timber.v(), Timber.d(), Timber.i(), Timber.w(), Timber.e(), Timber.wtf(). Избегайте прямого вызова Timber.log() с числовым priority — это снижает читаемость кода и усложняет рефакторинг.
Пятое правило — для библиотек и модулей используйте Timber.tag("CustomTag"). Этот метод возвращает временное дерево с переопределённым tag, не затрагивая глобальную конфигурацию. Это позволяет логировать из библиотечного кода с кастомным идентификатором.
Часто задаваемые вопросы
Да — Timber безопасно использовать в библиотеках. Если дерево не посажено в приложении, вызовы Timber не вызывают ошибок. Для библиотек рекомендуется использовать Timber.tag("LibraryTag") для идентификации источника логов.
Через стек вызовов (stack trace) — DebugTree поднимается на 8 фреймов вверх от точки вызова Timber.d() и извлекает имя класса. Метод Throwable.stackTrace используется для определения вызывающего класса без затрат Reflection API.
Logcat — системная утилита Android для просмотра логов. Timber — библиотека для написания логов. Timber выводит сообщения в Logcat через DebugTree, но также может отправлять их в файлы, Crashlytics, Sentry и другие каналы через кастомные деревья.
Нет — Timber завязан на Android SDK (android.util.Log). Для KMP-проектов рассмотрите Kermit или Napier — мультиплатформенные библиотеки логирования с похожей архитектурой деревьев, работающие на Android, iOS, JVM и JS.
Используйте Timber.uprootAll() — метод удаляет все зарегистрированные деревья. Timber.uproot(tree) удаляет конкретное дерево. Это полезно в тестах для сброса состояния между тестовыми методами.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также