Timber — лека библиотека за логване за Android с разширяема архитектура, базирана на дървета (Tree), която замени стандартния android.util.Log в хиляди проекти. Според данни от GitHub, 2024, библиотеката е събрала над 10 000 звезди и се използва в приложения с аудитория над 1 милиард инсталации. Timber решава три основни проблема на Log API: липса на автоматичен tag, задължителна проверка isLoggable и статичния характер на извикванията.
Основни точки
Timber — е библиотека с отворен код за Android, създадена от Джейк Уортън (Jake Wharton) през 2013 г. като алтернатива на стандартния android.util.Log. Ключовата идея на Timber — да замени статичния Log API със задължителен ръчен tag с автоматичен механизъм, който определя източника на извикване чрез стека.
Библиотеката е изградена върху архитектурния модел Composite с дървета (Tree). Вместо единствен Log клас с фиксирано поведение, Timber управлява „гора“ от дървета — всяко дърво отговаря за своя изходен канал: конзола, файл, Crashlytics, отдалечен сървър. Разработчикът може да добави произволен брой дървета и да ги комбинира.
Според данни от Google I/O 2019, Timber се препоръчва от Google като най-добра практика за логване в Android приложения. Библиотеката заема по-малко от 10 KB в 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(„съобщение”), библиотеката итеративно предава съобщението на всички дървета в реда на засаждане. Всяко дърво може да филтрира съобщението по ниво, tag или съдържание и да го обработи по свой начин.
Редът на засаждане е важен: първото засадено дърво се обработва първо. Препоръчва се да засадите DebugTree последен, така че персонализираните дървета (напр. Crashlytics) да обработят съобщението преди да стигне до Logcat.
Timber е безопасен за нишки — всички методи са синхронизирани чрез вътрешно заключване. Това гарантира, че съобщенията от различни нишки няма да се смесят. Вътре в персонализирано дърво обаче синхронизацията е отговорност на разработчика: ако дървото пише във файл, трябва да се използва 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 ще игнорира всички лог извиквания, без да хвърля изключения. Това е безопасно поведение по подразбиране: ако дървото не е засадено, библиотеката работи на празен ход с минимално натоварване.
Според данни от 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също