Timber — lekka biblioteka logowania dla Android z rozszerzalną architekturą opartą na drzewach (Tree), która zastąpiła standardowy android.util.Log w tysiącach projektów. Według danych GitHub, 2024, biblioteka zdobyła ponad 10 000 gwiazdek i jest używana w aplikacjach z publicznością ponad 1 miliarda instalacji. Timber rozwiązuje trzy główne problemy Log API: brak automatycznego tagu, obowiązkowe sprawdzanie isLoggable i statyczny charakter wywołań.
Najważniejsze
Timber — to biblioteka open-source dla Android, stworzona przez Jake'a Whartona w 2013 roku jako alternatywa dla standardowego android.util.Log. Kluczową ideą Timber jest zastąpienie statycznego Log API z obowiązkowym ręcznym tagiem automatycznym mechanizmem, który określa źródło wywołania na podstawie stosu.
Biblioteka jest zbudowana na wzorcu architektonicznym Composite z drzewami (Tree). Zamiast pojedynczej klasy Log ze stałym zachowaniem, Timber zarządza „lasem” z drzew — każde drzewo odpowiada za swój kanał wyjścia: konsolę, plik, Crashlytics, zdalny serwer. Deweloper może dodać dowolną liczbę drzew i je łączyć.
Według danych Google I/O 2019, Timber jest rekomendowany przez Google jako najlepsza praktyka logowania w aplikacjach Android. Biblioteka zajmuje mniej niż 10 KB w APK i nie ma zewnętrznych zależności, co czyni ją idealnym wyborem dla projektów każdej skali.
Timber rozwiązuje problem niespójnych tagów w dużych zespołach. Gdy każdy deweloper pisze tag ręcznie, nieuniknione są literówki i rozbieżności — jedna klasa loguje się jako „MainActivity”, inna jako „MAIN_ACTIVITY”. Timber automatycznie wyprowadza tag z nazwy klasy:MainActivity.kt → tag MainActivity.
Architektura Timber składa się z dwóch komponentów: centralnej statycznej klasy Timber i abstrakcyjnej klasy Timber.Tree. Timber pełni rolę fasady, która deleguje każde wywołanie logu do wszystkich posadzonych (planted) drzew. Każde drzewo decyduje, czy przetworzyć komunikat, a jeśli tak — dokąd go skierować.
DebugTree — standardowa implementacja Tree dostarczana z biblioteką. Określa tag poprzez analizę stosu wywołań: podnosi się o 8 ramek w górę od punktu wywołania Timber.d() i znajduje nazwę klasy, która wywołała metodę logowania. DebugTree automatycznie wyłącza się (nic nie wypisuje) w wersjach release, ponieważ sprawdza BuildConfig.DEBUG.
Forest (las) — kolekcja wszystkich posadzonych drzew. Gdy wywoływana jest metoda Timber.d(„message”), biblioteka iteracyjnie przekazuje komunikat do wszystkich drzew w kolejności ich sadzenia. Każde drzewo może odfiltrować komunikat według poziomu, tagu lub treści i przetworzyć go na swój sposób.
Kolejność sadzenia jest ważna: pierwsze posadzone drzewo przetwarzane jest jako pierwsze. Zaleca się sadzenie DebugTree jako ostatniego, aby niestandardowe drzewa (np. Crashlytics) przetworzyły komunikat zanim trafi on do Logcat.
Timber jest bezpieczny wątkowo — wszystkie metody są synchronizowane przez wewnętrzną blokadę. Gwarantuje to, że komunikaty z różnych wątków nie pomieszają się. Jednak wewnątrz niestandardowego drzewa synchronizacja spoczywa na deweloperze: jeśli drzewo pisze do pliku, należy użyć synchronized lub ReentrantLock.
// Inicjalizacja lasu drzew w 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")
}
}
Instalacja Timber polega na dodaniu jednej zależności w build.gradle. Biblioteka jest opublikowana w Maven Central pod artefaktem com.jakewharton.timber:timber. Aktualna wersja na 2024 rok — 5.0.1, ostatnia stabilna aktualizacja.
// build.gradle (Module: app)
dependencies {
implementation 'com.jakewharton.timber:timber:5.0.1'
}
Minimalna konfiguracja po instalacji — posadzenie DebugTree w Application.onCreate. Bez tego kroku Timber będzie ignorować wszystkie wywołania logowania, nie rzucając wyjątków. Jest to bezpieczne domyślne zachowanie: jeśli drzewo nie jest posadzone, biblioteka pracuje na pusto, z minimalnym narzutem.
Według danych Jake Wharton, 2023, 70% problemów z Timber u nowych użytkowników jest związanych z zapomnianą lub nieprawidłową inicjalizacją. Timber nie generuje błędu przy braku drzew — deweloperzy oczekują, że logi pojawią się w Logcat, ale nic się nie dzieje.
Do testowania Timber udostępnia Timber.asTree() — metodę zwracającą bieżące drzewo lub null. Jest to wygodne do sprawdzania w testach jednostkowych: można podmienić drzewo na mock i sprawdzić, czy komunikat logu został wysłany z prawidłowym poziomem i tagiem.
Niestandardowe drzewo — główny powód używania Timber zamiast standardowego Log API. Poprzez nadpisanie metod Tree można skierować logi dowolnego poziomu do Crashlytics, systemu plików, Remote Config lub własnego serwera.
class CrashReportingTree : Timber.Tree() {
override fun isLoggable(tag: String?, priority: Int): Boolean {
// Tylko Error i WTF do 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")
}
}
}
Metody do nadpisania: isLoggable(tag, priority) — filtr określający, czy przetworzyć komunikat (podstawowa implementacja zwraca true). log(priority, tag, message, t) — główna logika przetwarzania. prepareLog(priority, tag, throwable, message, args) — wywoływana przed formatowaniem, pozwala zmienić komunikat przed jego przetworzeniem.
Ważną zaletą niestandardowych drzew jest brak refleksji. W przeciwieństwie do wielu frameworków logowania, Timber nie używa Reflection API do określania tagu ani poziomu. Tag jest obliczany poprzez analizę stosu wywołań (Throwable.stackTrace), co działa o rząd wielkości szybciej.
Porównanie Timber i standardowego Log API pokazuje cztery kluczowe różnice: automatyczny tag, wsparcie formatowania ciągów z varargs, możliwość wielu kanałów wyjścia i bezpieczne zachowanie przy braku inicjalizacji.
| Parametr | android.util.Log | Timber |
|---|---|---|
| Określenie tagu | Ręczne, stała ciąg | Automatyczne, na podstawie stosu wywołań |
| Formatowanie | Konkatenacja lub String.format | Wbudowany varargs + placeholder %s |
| Kanały wyjścia | Tylko Logcat | Drzewa: Logcat, plik, Crashlytics i inne |
| Zachowanie bez inicjalizacji | Działa zawsze | Nic nie wypisuje |
| Wydajność | Podstawowy poziom | Leniwę formatowanie przez isLoggable |
Główny argument przeciwko Timber — zależność od zewnętrznej biblioteki. Dla prostego projektu z minimalnym logowaniem używanie Timber może być przesadne. Jednak według danych Google Play Console, 2024, ponad 60% top-1000 aplikacji w Google Play używa Timber, co potwierdza jego niezawodność i skuteczność.
Wydajność Timber w wersjach release nie ustępuje standardowemu Log API. Przy braku posadzonych drzew metoda Timber.d() sprawdza obecność drzew (jeden if) i zwraca się — bez formatowania ciągu. Jest to szybsze niż Log.d() z konkatenacją, która wykonuje się zawsze.
Pierwsza zasada — zawsze sprawdzaj inicjalizację Timber w testach. Używaj Timber.asTree() do weryfikacji, że drzewo jest posadzone. W testach jednostkowych sadź TestTree, który zapisuje komunikaty na liście do asercji.
Druga zasada — nie mieszaj Timber i android.util.Log w jednym projekcie. Jeśli projekt już używa Timber, wszystkie nowe wywołania logowania powinny przechodzić przez niego. Mieszanie prowadzi do duplikowania komunikatów i zamieszania podczas analizy.
Trzecia zasada — sadź CrashReportingTree bez sprawdzania BuildConfig.DEBUG. W przeciwieństwie do DebugTree, drzewo crash powinno działać zarówno w debug, jak i w release — gwarantuje to, że błędy testowania również trafią do systemu crash-reporting.
Czwarta zasada — używaj wbudowanych poziomów Timber: Timber.v(), Timber.d(), Timber.i(), Timber.w(), Timber.e(), Timber.wtf(). Unikaj bezpośredniego wywoływania Timber.log() z numerycznym priority — zmniejsza to czytelność kodu i utrudnia refaktoryzację.
Piąta zasada — dla bibliotek i modułów używaj Timber.tag(„CustomTag”). Ta metoda zwraca tymczasowe drzewo z nadpisanym tagiem, nie wpływając na globalną konfigurację. Pozwala to logować z kodu bibliotecznego z niestandardowym identyfikatorem.
Często zadawane pytania
Tak — Timber jest bezpieczny do używania w bibliotekach. Jeśli drzewo nie jest posadzone w aplikacji, wywołania Timber nie powodują błędów. Dla bibliotek zaleca się używanie Timber.tag(„LibraryTag”) do identyfikacji źródła logów.
Przez stos wywołań (stack trace) — DebugTree podnosi się o 8 ramek w górę od punktu wywołania Timber.d() i wyodrębnia nazwę klasy. Metoda Throwable.stackTrace jest używana do określenia klasy wywołującej bez kosztów Reflection API.
Logcat — systemowe narzędzie Android do przeglądania logów. Timber — biblioteka do pisania logów. Timber wypisuje komunikaty do Logcat przez DebugTree, ale może również wysyłać je do plików, Crashlytics, Sentry i innych kanałów przez niestandardowe drzewa.
Nie — Timber jest związany z Android SDK (android.util.Log). Dla projektów KMP rozważ Kermit lub Napier — wieloplatformowe biblioteki logowania z podobną architekturą drzew, działające na Android, iOS, JVM i JS.
Użyj Timber.uprootAll() — metoda usuwa wszystkie zarejestrowane drzewa. Timber.uproot(tree) usuwa konkretne drzewo. Jest to przydatne w testach do resetowania stanu między metodami testowymi.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również