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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також